recuperare i collegamenti dalla pagina Web utilizzando Python e BeautifulSoup


Risposte:


193

Ecco un breve frammento che utilizza la classe SoupStrainer in BeautifulSoup:

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'])

La documentazione di BeautifulSoup è in realtà abbastanza buona e copre una serie di scenari tipici:

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

Modifica: nota che ho usato la classe SoupStrainer perché è un po 'più efficiente (in termini di memoria e velocità), se sai cosa stai analizzando in anticipo.


13
+1, usare il filtro per zuppa è un'ottima idea perché ti consente di aggirare un sacco di analisi non necessarie quando tutto ciò che cerchi sono i link.
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
Sulla versione 3.2.1 di BeautifulSoup non esiste has_attr. Invece vedo che c'è qualcosa chiamato has_keye funziona.

2
Aggiornamento per python3
john doe,

7
da bs4 importare BeautifulSoup. (non da BeautifulSoup importare BeautifulSoup ..) correzione necessaria.
Rishabh Agrahari,

67

Per completezza, la versione BeautifulSoup 4, utilizzando anche la codifica fornita dal server:

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'])

o la versione di Python 2:

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']

e una versione che utilizza la requestslibreria , che come scritto funzionerà sia in Python 2 che in 3:

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'])

La soup.find_all('a', href=True)chiamata trova tutti gli <a>elementi che hanno un hrefattributo; gli elementi senza l'attributo vengono saltati.

BeautifulSoup 3 ha interrotto lo sviluppo a marzo 2012; i nuovi progetti dovrebbero davvero usare BeautifulSoup 4, sempre.

Nota che dovresti lasciare la decodifica dell'HTML da byte a BeautifulSoup . È possibile informare BeautifulSoup del set di caratteri trovato nelle intestazioni della risposta HTTP per facilitare la decodifica, ma ciò può essere errato e in conflitto con le <meta>informazioni di intestazione trovate nell'HTML stesso, motivo per cui quanto sopra utilizza il metodo di classe interno BeautifulSoup EncodingDetector.find_declared_encoding()per assicurarsi che tali suggerimenti di codifica incorporati vincono su un server configurato in modo errato.

Con requests, l' response.encodingattributo viene impostato automaticamente su Latin-1 se la risposta ha un text/*mimetipo, anche se non è stato restituito alcun set di caratteri. Ciò è coerente con gli RFC HTTP ma è doloroso se utilizzato con l'analisi HTML, quindi è necessario ignorare tale attributo quando non charsetè impostato alcun valore nell'intestazione Content-Type.


C'è qualcosa come StrainedSoup per bs4? (Non ne ho bisogno ora, ma mi chiedo se c'è qualcosa che potresti voler aggiungere)
Antti Haapala,

@AnttiHaapala: SoupStrainervuoi dire? Non è andato da nessuna parte, fa ancora parte del progetto .
Martijn Pieters

C'è un motivo per cui questo codice non passa "features =" al costruttore BeautifulSoup? BeautifulSoup mi dà un avvertimento sull'uso di un parser predefinito.
MikeB,

1
@MikeB: quando ho scritto questa risposta BeautifulSoup non ha ancora emesso un avviso se non l'hai fatto.
Martijn Pieters

50

Altri hanno consigliato BeautifulSoup, ma è molto meglio usare lxml . Nonostante il suo nome, è anche per l'analisi e lo scraping dell'HTML. È molto, molto più veloce di BeautifulSoup e gestisce anche l'HTML "rotto" meglio di BeautifulSoup (la loro pretesa di fama). Ha un'API di compatibilità anche per BeautifulSoup se non vuoi imparare l'API lxml.

Ian Blicking è d'accordo .

Non c'è più motivo di utilizzare BeautifulSoup, a meno che tu non sia su Google App Engine o qualcosa in cui non sia consentito nulla che non sia puramente Python.

lxml.html supporta anche i selettori CSS3, quindi questo genere di cose è banale.

Un esempio con lxml e xpath sarebbe simile al seguente:

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 utilizzerà lxmlcome parser predefinito se installato.
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'

Questo ha risolto un problema che avevo con il mio codice. Grazie!
RJ,

10

Il codice seguente consente di recuperare tutti i collegamenti disponibili in una pagina Web utilizzando urllib2e 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

Sotto il cofano BeautifulSoup ora utilizza lxml. Richieste, lxml e comprensione delle liste rendono una combo killer.

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]

Nell'elenco comp, "if '//' e 'url.com' non in x" è un metodo semplice per cancellare l'elenco di URL degli URL di navigazione "interni" dei siti, ecc.


1
Se è un repost, perché il post originale non include: 1. richieste 2.list comp 3. logica per cancellare i collegamenti interni e junk del sito ?? Prova a confrontare i risultati dei due post, la mia lista comp fa un lavoro sorprendentemente buono strofinando i collegamenti spazzatura.
cheekybastard,

L'OP non ha richiesto quelle funzionalità e la parte che ha richiesto è già stata pubblicata e risolta utilizzando lo stesso metodo che pubblichi. Tuttavia, rimuoverò il downvote poiché la comprensione dell'elenco aggiunge valore per le persone che desiderano tali funzionalità e le menzioni esplicitamente nel corpo del post. Inoltre, potresti usare il rappresentante :)
dotancohen il

4

solo per ottenere i collegamenti, senza B.soup e regex:

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]

per operazioni più complesse, ovviamente BSoup è ancora preferito.


7
E se, per esempio, c'è qualcosa tra <ae href? Dire rel="nofollow"o onclick="..."anche solo una nuova linea? stackoverflow.com/questions/1732348/...
dimo414

c'è un modo per filtrare solo alcuni link con questo? come dire che voglio solo i collegamenti che ha "episodio" nel collegamento?
nwgat,

4

Questo script fa quello che stai cercando, ma risolve anche i collegamenti relativi ai collegamenti assoluti.

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

Questo non fa ciò che ti intende fare; se resol_links () non ha una radice, quindi non restituisce mai alcun URL.
MikeB,

4

Per trovare tutti i collegamenti, in questo esempio useremo il modulo urllib2 insieme a re.module * Una delle funzioni più potenti nel modulo re è "re.findall ()". Mentre re.search () viene utilizzato per trovare la prima corrispondenza per un modello, re.findall () trova tutte le corrispondenze e le restituisce come un elenco di stringhe, con ciascuna stringa che rappresenta una corrispondenza *

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

Perché non usare le espressioni regolari:

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
mi piacerebbe essere in grado di capirlo, dove posso scoprire in modo efficiente cosa (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)significa? Grazie!
user1063287

9
Davvero una cattiva idea. HTML rotto ovunque.
Ufoguy,

2
Perché non usare le espressioni regolari per analizzare HTML: stackoverflow.com/questions/1732348/...
allcaps

@ user1063287, il web è pieno di tutorial regex. Vale la pena dedicare del tempo a leggere un paio. Mentre le RE possono essere davvero contorte, quella di cui stai chiedendo è piuttosto semplice.
alexis,

3

I collegamenti possono essere all'interno di una varietà di attributi in modo da poter passare un elenco di quegli attributi da selezionare

ad esempio, con l'attributo src e href (qui sto usando l'operatore inizia con ^ per specificare che uno di questi valori di attributi inizia con http. È possibile personalizzare questo come richiesto

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)

Attributo = selettori di valore

[Attr ^ = value]

Rappresenta elementi con un nome di attributo di attr il cui valore è preceduto (preceduto) da valore.


1

Ecco un esempio utilizzando @ars risposta accettata e le BeautifulSoup4, requestse wgetmoduli per gestire i download.

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

Ho trovato la risposta di @ Blairg23 funzionante, dopo la seguente correzione (che copre lo scenario in cui non ha funzionato correttamente):

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)

Per Python 3:

urllib.parse.urljoin deve essere utilizzato per ottenere invece l'URL completo.


1

Lo stesso parser di BeatifulSoup può essere lento. Potrebbe essere più fattibile usare lxml che è in grado di analizzare direttamente da un URL (con alcune limitazioni menzionate di seguito).

import lxml.html

doc = lxml.html.parse(url)

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

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

Il codice sopra restituirà i collegamenti così come sono, e nella maggior parte dei casi sarebbero collegamenti relativi o assoluti dalla radice del sito. Dal momento che il mio caso d'uso è stato quello di estrarre solo un certo tipo di collegamenti, di seguito è una versione che converte i collegamenti in URL completi e che facoltativamente accetta un modello glob come *.mp3. Tuttavia, non gestirà punti singoli e doppi nei relativi percorsi, ma finora non ne ho avuto bisogno. Se devi analizzare frammenti di URL contenenti ../o ./quindi urlparse.urljoin potrebbe tornare utile.

NOTA : l'analisi dell'URL lxml diretto non gestisce il caricamento da httpse non esegue reindirizzamenti, quindi per questa ragione la versione seguente utilizza urllib2+ 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

L'utilizzo è il seguente:

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"

lxmlpuò gestire solo input validi, come può sostituirli BeautifulSoup?
alexis,

@alexis: penso che lxml.htmlsia un po 'più indulgente del lxml.etree. Se il tuo input non è ben formato, puoi impostare esplicitamente il parser BeautifulSoup: lxml.de/elementsoup.html . E se vai con BeatifulSoup, BS3 è la scelta migliore.
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

Possono esserci molti collegamenti duplicati insieme a collegamenti sia esterni che interni. Per distinguere tra i due e ottenere solo collegamenti univoci utilizzando i set:

# 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.