python ve BeautifulSoup kullanarak web sayfasındaki bağlantıları al


Yanıtlar:


193

İşte BeautifulSoup'ta SoupStrainer sınıfını kullanan kısa bir snippet:

import httplib2
from bs4 import BeautifulSoup, SoupStrainer

http = httplib2.Http()
status, response = http.request('http://www.nytimes.com')

for link in BeautifulSoup(response, parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        print(link['href'])

BeautifulSoup belgeleri aslında oldukça iyidir ve bir dizi tipik senaryoyu kapsar:

https://www.crummy.com/software/BeautifulSoup/bs4/doc/

Düzenleme: Ben önceden ayrıştırma biliyorsanız, biraz daha verimli (bellek ve hız akıllıca) çünkü SoupStrainer sınıf kullandığımı unutmayın.


13
+1, çorba süzgecini kullanmak harika bir fikir çünkü bağlantıların hepsi peşinde olduğunuzda çok fazla gereksiz ayrışmayı atlamanızı sağlar.
Evan Fosmark

4
Heads up:/usr/local/lib/python2.7/site-packages/bs4/__init__.py:128: UserWarning: The "parseOnlyThese" argument to the BeautifulSoup constructor has been renamed to "parse_only."
BenDundee

28
BeautifulSoup'un 3.2.1 sürümünde hayır has_attr. Bunun yerine denilen bir şey olduğunu görüyorum has_keyve işe yarıyor.

2
Python3 için güncelleme
john doe

7
bs4 ithalat BeautifulSoup dan. (BeautifulSoup'tan içe aktarma BeautifulSoup ..) düzeltmesi gerekiyor.
Rishabh Agrahari

67

Tamlık uğruna, sunucu tarafından sağlanan kodlamayı kullanarak BeautifulSoup 4 sürümü:

from bs4 import BeautifulSoup
import urllib.request

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib.request.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().get_param('charset'))

for link in soup.find_all('a', href=True):
    print(link['href'])

veya Python 2 sürümü:

from bs4 import BeautifulSoup
import urllib2

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = urllib2.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, parser, from_encoding=resp.info().getparam('charset'))

for link in soup.find_all('a', href=True):
    print link['href']

ve requestskitaplığı kullanan ve Python 2 ve 3'te yazılan bir sürüm :

from bs4 import BeautifulSoup
from bs4.dammit import EncodingDetector
import requests

parser = 'html.parser'  # or 'lxml' (preferred) or 'html5lib', if installed
resp = requests.get("http://www.gpsbasecamp.com/national-parks")
http_encoding = resp.encoding if 'charset' in resp.headers.get('content-type', '').lower() else None
html_encoding = EncodingDetector.find_declared_encoding(resp.content, is_html=True)
encoding = html_encoding or http_encoding
soup = BeautifulSoup(resp.content, parser, from_encoding=encoding)

for link in soup.find_all('a', href=True):
    print(link['href'])

soup.find_all('a', href=True)Çağrı bütün bulur <a>bir sahip elemanlarhref niteliğini; niteliği olmayan öğeler atlanır.

BeautifulSoup 3, Mart 2012'de geliştirmeyi durdurdu; yeni projeler her zaman BeautifulSoup 4'ü kullanmalıdır.

HTML kodunun bayttan BeautifulSoup'a kodunun çözülmesi gerektiğini unutmayın . Kod çözme işlemine yardımcı olması için HTTP yanıt başlıklarında bulunan karakter kümesini BeautifulSoup'a bildirebilirsiniz, ancak bu yanlış olabilir<meta> ve HTML'nin kendisinde bulunan bir başlık bilgisi ile çakışabilir , bu nedenle yukarıdaki BeautifulSoup iç sınıf yöntemini kullanırEncodingDetector.find_declared_encoding() emin olmak için bu tür gömülü kodlama ipuçları, yanlış yapılandırılmış bir sunucu üzerinden kazanır.

İle requests, hiçbir karakter kümesi döndürülmemiş olsa bile response.encodingyanıtın bir text/*mime türü varsa öznitelik varsayılan olarak Latin-1 olur . Bu HTTP RFC'leri ile tutarlıdır, ancak HTML ayrıştırmasıyla kullanıldığında acı vericidir, bu nedenle charsetContent-Type üstbilgisinde ayarlanmadığında bu özelliği yok saymalısınız.


BS4 için StrainedSoup gibi bir şey var mı? (Şimdi ihtiyacım yok ama merak ediyorum, eğer eklemek istersen)
Antti Haapala

@AnttiHaapala: SoupStraineryani? O hala projenin parçasıdır, yere gitmedim .
Martijn Pieters

Bu kodun BeautifulSoup yapıcısına "features =" iletmemesinin bir nedeni var mı? BeautifulSoup bana varsayılan ayrıştırıcı kullanma hakkında bir uyarı veriyor.
MikeB

1
@MikeB: Bu cevabı yazdığımda BeautifulSoup henüz bir uyarı yapmadı.
Martijn Pieters

50

Diğerleri BeautifulSoup'u tavsiye etti, ancak lxml kullanmak çok daha iyi . Adına rağmen, HTML'yi ayrıştırmak ve kazıma amaçlıdır. BeautifulSoup'tan çok, çok daha hızlı ve hatta "kırık" HTML'yi BeautifulSoup'tan (şöhret iddiası) daha iyi işler. Eğer lxml API'sını öğrenmek istemiyorsanız BeautifulSoup için bir uyumluluk API'sı vardır.

Ian Blicking aynı fikirde .

Google App Engine'de veya yalnızca Python'a izin verilmeyen bir şeye izin vermediğiniz sürece BeautifulSoup'u kullanmanız için hiçbir neden yoktur.

lxml.html de CSS3 seçicileri destekler, bu nedenle bu tür şeyler önemsizdir.

Lxml ve xpath ile bir örnek şöyle görünür:

import urllib
import lxml.html
connection = urllib.urlopen('http://www.nytimes.com')

dom =  lxml.html.fromstring(connection.read())

for link in dom.xpath('//a/@href'): # select the url in href for all a tags(links)
    print link

23
BeautifulSoup 4 lxmlkurulu ise varsayılan ayrıştırıcı olarak kullanılır.
Martijn Pieters

28
import urllib2
import BeautifulSoup

request = urllib2.Request("http://www.gpsbasecamp.com/national-parks")
response = urllib2.urlopen(request)
soup = BeautifulSoup.BeautifulSoup(response)
for a in soup.findAll('a'):
  if 'national-park' in a['href']:
    print 'found a url with national-park in the link'

Bu benim kod ile yaşadım bir sorunu çözdü. Teşekkür ederim!
RJ

10

Aşağıdaki kod, urllib2ve kullanarak bir web sayfasındaki tüm bağlantıları almak içindir BeautifulSoup4:

import urllib2
from bs4 import BeautifulSoup

url = urllib2.urlopen("http://www.espncricinfo.com/").read()
soup = BeautifulSoup(url)

for line in soup.find_all('a'):
    print(line.get('href'))

8

Başlık altında BeautifulSoup şimdi lxml kullanıyor. İstekler, lxml ve liste kavrayışları katil birleşik yapar.

import requests
import lxml.html

dom = lxml.html.fromstring(requests.get('http://www.nytimes.com').content)

[x for x in dom.xpath('//a/@href') if '//' in x and 'nytimes.com' not in x]

Comp listesinde, "if '//' ve 'url.com' x içinde değil", "dahili" gezinme URL'lerinin vb. Sitelerinin url listesini temizlemek için basit bir yöntemdir.


1
Bir repost ise, neden orijinal gönderi içermiyor: 1. istekleri 2.list comp 3. site iç ve önemsiz bağlantıları fırçalamak için mantık ?? İki gönderinin sonuçlarını karşılaştırmaya çalışın, listemiz comp önemsiz bağlantıları ovma şaşırtıcı derecede iyi bir iş yapar.
cheekybastard

OP bu özellikleri istemedi ve istediği bölüm, yayınladığınız yöntemle zaten gönderildi ve çözüldü. Ancak, liste kavraması bu özellikleri isteyen insanlar için değer kattığından ve yayının gövdesinden açıkça bahsettiğinizden aşağı oyu kaldıracağım. Ayrıca, rep kullanabilirsiniz :)
dotancohen

4

sadece bağlantıları almak için, B.soup ve regex olmadan:

import urllib2
url="http://www.somewhere.com"
page=urllib2.urlopen(url)
data=page.read().split("</a>")
tag="<a href=\""
endtag="\">"
for item in data:
    if "<a href" in item:
        try:
            ind = item.index(tag)
            item=item[ind+len(tag):]
            end=item.index(endtag)
        except: pass
        else:
            print item[:end]

daha karmaşık işlemler için elbette BSoup hala tercih edilmektedir.


7
Örneğin, <ave href? Arasında bir şey varsa ? Say rel="nofollow"veya onclick="..."hatta sadece yeni hat? stackoverflow.com/questions/1732348/…
dimo414 12:12

bununla sadece bazı bağlantıları filtrelemenin bir yolu var mı? sadece link "Bölüm" bağlantıları istiyorum demek gibi?
nwgat

4

Bu komut dosyası aradığınızı yapar, ancak mutlak bağlantılara ilişkin göreli bağlantıları da çözer.

import urllib
import lxml.html
import urlparse

def get_dom(url):
    connection = urllib.urlopen(url)
    return lxml.html.fromstring(connection.read())

def get_links(url):
    return resolve_links((link for link in get_dom(url).xpath('//a/@href')))

def guess_root(links):
    for link in links:
        if link.startswith('http'):
            parsed_link = urlparse.urlparse(link)
            scheme = parsed_link.scheme + '://'
            netloc = parsed_link.netloc
            return scheme + netloc

def resolve_links(links):
    root = guess_root(links)
    for link in links:
        if not link.startswith('http'):
            link = urlparse.urljoin(root, link)
        yield link  

for link in get_links('http://www.google.com'):
    print link

Bu, ti'nin yapmak istediği şeyi yapmaz; resol_links () 'nin bir kökü yoksa, hiçbir zaman URL döndürmez.
MikeB

4

Tüm bağlantıları bulmak için, bu örnekte urllib2 modülünü re.module ile birlikte kullanacağız * re modülündeki en güçlü işlevlerden biri "re.findall ()" dir. Bir desenin ilk eşleşmesini bulmak için re.search () kullanılırken, re.findall () tüm eşleşmeleribulurve her dizeyi bir eşleşmeyi temsil edecek şekilde dizeler listesi olarak döndürür *

import urllib2

import re
#connect to a URL
website = urllib2.urlopen(url)

#read html code
html = website.read()

#use re.findall to get all the links
links = re.findall('"((http|ftp)s?://.*?)"', html)

print links

3

Neden düzenli ifadeler kullanılmıyor:

import urllib2
import re
url = "http://www.somewhere.com"
page = urllib2.urlopen(url)
page = page.read()
links = re.findall(r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)
for link in links:
    print('href: %s, HTML text: %s' % (link[0], link[1]))

1
Bunu anlayabilmeyi çok isterim, ne (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)anlama geldiğini nerede bulabilirim ? Teşekkürler!
user1063287

9
Gerçekten kötü bir fikir. Her yerde bozuk HTML.
Ufoguy

2
Neden normal ifadeler kullanmaz ayrıştırma html: stackoverflow.com/questions/1732348/...
allcaps

@ user1063287, web normal ifade eğiticileriyle doludur. Bir çift okumak için zaman ayırmaya değer. RE'ler gerçekten kıvrılabilirken, sorduğunuz şey oldukça basit.
alexis

3

Bağlantılar çeşitli özellikler içinde olabilir, böylece seçmek için bu özelliklerin bir listesini iletebilirsiniz

Örneğin, src ve href özniteliğiyle (burada, bu öznitelik değerlerinden birinin http ile başlayacağını belirtmek için ^ işleciyle başlıyorum. Bunu gerektiği gibi uyarlayabilirsiniz.

from bs4 import BeautifulSoup as bs
import requests
r = requests.get('https://stackoverflow.com/')
soup = bs(r.content, 'lxml')
links = [item['href'] if item.get('href') is not None else item['src'] for item in soup.select('[href^="http"], [src^="http"]') ]
print(links)

Özellik = değer seçiciler

[Attr ^ = değer]

Attr öznitelik adına sahip ve değeri değerin önüne (önüne) geçen öğeleri temsil eder.


1

İşte @ars kabul cevabı ve kullanıldığı bir örnek BeautifulSoup4, requestsve wgetindirme işlemleri için modüller.

import requests
import wget
import os

from bs4 import BeautifulSoup, SoupStrainer

url = 'https://archive.ics.uci.edu/ml/machine-learning-databases/eeg-mld/eeg_full/'
file_type = '.tar.gz'

response = requests.get(url)

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path = url + link['href']
            wget.download(full_path)

1

Aşağıdaki düzeltmeden sonra (Blair'in düzgün çalışamadığı senaryoyu kapsayan) @ Blairg23'ün yanıtını buldum:

for link in BeautifulSoup(response.content, 'html.parser', parse_only=SoupStrainer('a')):
    if link.has_attr('href'):
        if file_type in link['href']:
            full_path =urlparse.urljoin(url , link['href']) #module urlparse need to be imported
            wget.download(full_path)

Python 3 için:

urllib.parse.urljoin bunun yerine tam URL'yi elde etmek için kullanılmalıdır.


1

BeatifulSoup'un kendi ayrıştırıcısı yavaş olabilir. Doğrudan bir URL'den ayrıştırabilen (aşağıda belirtilen bazı sınırlamalarla) lxml kullanmak daha uygun olabilir .

import lxml.html

doc = lxml.html.parse(url)

links = doc.xpath('//a[@href]')

for link in links:
    print link.attrib['href']

Yukarıdaki kod bağlantıları olduğu gibi döndürür ve çoğu durumda göreli bağlantılar veya site kökünden mutlak olurlar. Kullanım durumum yalnızca belirli bir tür bağlantıyı ayıklamak olduğundan, aşağıda bağlantıları tam URL'lere dönüştüren ve isteğe bağlı olarak bir glob kalıbını kabul eden bir sürüm bulunmaktadır *.mp3. Bununla birlikte, göreli yollarda tek ve çift noktaları işlemez, ancak şu ana kadar buna ihtiyacım yoktu. Eğer içeren ayrıştırma URL parçalarına gerekiyorsa ../veya ./sonra urlparse.urljoin işine yarayacaktır.

NOT : Doğrudan lxml url ayrıştırma işlemi, yükleme işlemini gerçekleştirmez httpsve yönlendirmeler yapmaz, bu nedenle aşağıdaki sürümde urllib2+ kullanılır lxml.

#!/usr/bin/env python
import sys
import urllib2
import urlparse
import lxml.html
import fnmatch

try:
    import urltools as urltools
except ImportError:
    sys.stderr.write('To normalize URLs run: `pip install urltools --user`')
    urltools = None


def get_host(url):
    p = urlparse.urlparse(url)
    return "{}://{}".format(p.scheme, p.netloc)


if __name__ == '__main__':
    url = sys.argv[1]
    host = get_host(url)
    glob_patt = len(sys.argv) > 2 and sys.argv[2] or '*'

    doc = lxml.html.parse(urllib2.urlopen(url))
    links = doc.xpath('//a[@href]')

    for link in links:
        href = link.attrib['href']

        if fnmatch.fnmatch(href, glob_patt):

            if not href.startswith(('http://', 'https://' 'ftp://')):

                if href.startswith('/'):
                    href = host + href
                else:
                    parent_url = url.rsplit('/', 1)[0]
                    href = urlparse.urljoin(parent_url, href)

                    if urltools:
                        href = urltools.normalize(href)

            print href

Kullanımı aşağıdaki gibidir:

getlinks.py http://stackoverflow.com/a/37758066/191246
getlinks.py http://stackoverflow.com/a/37758066/191246 "*users*"
getlinks.py http://fakedomain.mu/somepage.html "*.mp3"

lxmlsadece geçerli girdiyi işleyebilir, nasıl değiştirebilir BeautifulSoup?
alexis

@alexis: Bence lxml.htmlbiraz daha yumuşak lxml.etree. Girişiniz iyi biçimlendirilmemişse, BeautifulSoup ayrıştırıcısını açıkça ayarlayabilirsiniz: lxml.de/elementsoup.html . BeatifulSoup ile giderseniz, BS3 daha iyi bir seçimdir.
ccpizza

0
import urllib2
from bs4 import BeautifulSoup
a=urllib2.urlopen('http://dir.yahoo.com')
code=a.read()
soup=BeautifulSoup(code)
links=soup.findAll("a")
#To get href part alone
print links[0].attrs['href']

0

Hem harici hem de dahili bağlantılarla birlikte birçok yinelenen bağlantı olabilir. İkisi arasında ayrım yapmak ve setleri kullanarak benzersiz bağlantılar elde etmek için:

# Python 3.
import urllib    
from bs4 import BeautifulSoup

url = "http://www.espncricinfo.com/"
resp = urllib.request.urlopen(url)
# Get server encoding per recommendation of Martijn Pieters.
soup = BeautifulSoup(resp, from_encoding=resp.info().get_param('charset'))  
external_links = set()
internal_links = set()
for line in soup.find_all('a'):
    link = line.get('href')
    if not link:
        continue
    if link.startswith('http'):
        external_links.add(link)
    else:
        internal_links.add(link)

# Depending on usage, full internal links may be preferred.
full_internal_links = {
    urllib.parse.urljoin(url, internal_link) 
    for internal_link in internal_links
}

# Print all unique external and full internal links.
for link in external_links.union(full_internal_links):
    print(link)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.