Rufen Sie mit Python und BeautifulSoup Links von der Webseite ab

Antworten:

193

Hier ist ein kurzer Ausschnitt aus der SoupStrainer-Klasse 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'])

Die BeautifulSoup-Dokumentation ist eigentlich recht gut und deckt eine Reihe typischer Szenarien ab:

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

Bearbeiten: Beachten Sie, dass ich die SoupStrainer-Klasse verwendet habe, weil sie etwas effizienter ist (Speicher und Geschwindigkeit), wenn Sie im Voraus wissen, was Sie analysieren.

ars
quelle
13
+1, die Verwendung des Suppensiebs ist eine großartige Idee, da Sie so viele unnötige Analysen umgehen können, wenn Sie nur die Links suchen.
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
27
In Version 3.2.1 von BeautifulSoup gibt es keine has_attr. Stattdessen sehe ich, dass es etwas gibt, das heißt has_keyund es funktioniert.
2
Update für Python3
John Doe
7
von bs4 importieren BeautifulSoup. (nicht von BeautifulSoup importieren BeautifulSoup ..) Korrektur erforderlich.
Rishabh Agrahari
67

Der Vollständigkeit halber die BeautifulSoup 4-Version, die auch die vom Server bereitgestellte Codierung verwendet:

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

oder die Python 2-Version:

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

und eine Version mit der requestsBibliothek , die wie geschrieben sowohl in Python 2 als auch in Python 3 funktioniert:

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

Der soup.find_all('a', href=True)Aufruf findet alle <a>Elemente, die ein hrefAttribut haben. Elemente ohne das Attribut werden übersprungen.

BeautifulSoup 3 hat die Entwicklung im März 2012 eingestellt. Neue Projekte sollten wirklich immer BeautifulSoup 4 verwenden.

Beachten Sie, dass Sie den HTML-Code von Bytes in BeautifulSoup dekodieren sollten . Sie können BeautifulSoup über den Zeichensatz informieren, der in den HTTP-Antwortheadern enthalten ist, um die Dekodierung zu unterstützen. Dies kann jedoch falsch sein und mit <meta>den im HTML selbst enthaltenen Headerinformationen in Konflikt stehen. Aus diesem Grund wird die interne Klassenmethode BeautifulSoup verwendet EncodingDetector.find_declared_encoding(), um dies sicherzustellen Solche eingebetteten Codierungshinweise überzeugen einen falsch konfigurierten Server.

Bei requestsist das response.encodingAttribut standardmäßig Latin-1, wenn die Antwort einen text/*Mimetyp hat, auch wenn kein Zeichensatz zurückgegeben wurde. Dies stimmt mit den HTTP-RFCs überein, ist jedoch bei der HTML-Analyse schmerzhaft. Daher sollten Sie dieses Attribut ignorieren, wenn charsetim Content-Type-Header no festgelegt ist.

Martijn Pieters
quelle
Gibt es so etwas wie StrainedSoup für bs4? (Ich brauche es jetzt nicht, aber ich frage mich nur, ob es das gibt, das Sie vielleicht hinzufügen möchten)
Antti Haapala
@AnttiHaapala: SoupStrainermeinst du? Es ging nirgendwo hin, es ist immer noch Teil des Projekts .
Martijn Pieters
Gibt es einen Grund, warum dieser Code "features =" nicht an den BeautifulSoup-Konstruktor übergibt? BeautifulSoup warnt mich vor der Verwendung eines Standardparsers.
MikeB
1
@ MikeB: Als ich diese Antwort schrieb, hat BeautifulSoup noch keine Warnung ausgegeben, wenn Sie dies nicht getan haben.
Martijn Pieters
50

Andere haben BeautifulSoup empfohlen, aber es ist viel besser, lxml zu verwenden . Trotz seines Namens dient es auch zum Parsen und Scraping von HTML. Es ist viel, viel schneller als BeautifulSoup und es handhabt sogar "kaputtes" HTML besser als BeautifulSoup (ihr Anspruch auf Ruhm). Es hat auch eine Kompatibilitäts-API für BeautifulSoup, wenn Sie die lxml-API nicht lernen möchten.

Ian Blicking stimmt zu .

Es gibt keinen Grund mehr, BeautifulSoup zu verwenden, es sei denn, Sie verwenden Google App Engine oder etwas, bei dem etwas, das nicht nur Python ist, nicht zulässig ist.

lxml.html unterstützt auch CSS3-Selektoren, so dass solche Dinge trivial sind.

Ein Beispiel mit lxml und xpath würde folgendermaßen aussehen:

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
aehlke
quelle
23
BeautifulSoup 4 wird lxmlbei Installation als Standardparser verwendet.
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'
Andrew Johnson
quelle
Dies löste ein Problem, das ich mit meinem Code hatte. Danke dir!
RJ
10

Der folgende Code dient zum Abrufen aller auf einer Webseite verfügbaren Links mit urllib2und 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'))
Sentient07
quelle
8

Unter der Haube verwendet BeautifulSoup jetzt lxml. Anfragen, lxml & Listenverständnis machen eine Killer-Combo.

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]

In der Liste comp ist das "if '//' und 'url.com' not in x" eine einfache Methode, um die URL-Liste der 'internen' Navigations-URLs der Websites usw. zu bereinigen.

frecher Bastard
quelle
1
Wenn es sich um einen Repost handelt, warum enthält der ursprüngliche Beitrag nicht: 1. Anforderungen 2. Liste comp 3. Logik zum Scrubben von internen und Junk-Links? Versuchen Sie, die Ergebnisse der beiden Beiträge zu vergleichen. Mein Listen-Comp macht einen überraschend guten Job beim Scrubben der Junk-Links.
frecher Bastard
Das OP hat nicht nach diesen Funktionen gefragt, und der Teil, nach dem er gefragt hat, wurde bereits veröffentlicht und mit genau der gleichen Methode gelöst, die Sie veröffentlicht haben. Ich werde jedoch die Ablehnung entfernen, da das Listenverständnis einen Mehrwert für Personen darstellt, die diese Funktionen wünschen, und Sie sie im Hauptteil des Beitrags ausdrücklich erwähnen. Sie können auch den Repräsentanten verwenden :)
dotancohen
4

Nur um die Links zu erhalten, ohne B.soup und 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]

Für komplexere Operationen wird BSoup natürlich immer noch bevorzugt.

Ghostdog74
quelle
7
Und wenn zum Beispiel etwas dazwischen liegt <aund href? Sagen Sie rel="nofollow"oder onclick="..."oder nur eine neue Zeile? stackoverflow.com/questions/1732348/…
dimo414
Gibt es eine Möglichkeit, nur einige Links damit herauszufiltern? Wie sagen wir, ich möchte nur Links, die "Episode" im Link haben?
Nwgat
4

Dieses Skript macht das, wonach Sie suchen, löst aber auch die relativen Links zu absoluten Links auf.

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
Ricky Wilson
quelle
Dies macht nicht das, was es zu tun hat; Wenn resolve_links () kein Root hat, werden keine URLs zurückgegeben.
MikeB
4

Um alle Links zu finden, verwenden wir in diesem Beispiel das Modul urllib2 zusammen mit dem Modul re.module. * Eine der leistungsstärksten Funktionen im Modul re ist "re.findall ()". Während re.search () verwendet wird, um die erste Übereinstimmung für ein Muster zu finden, findet re.findall () alle Übereinstimmungen und gibt sie als Liste von Zeichenfolgen zurück, wobei jede Zeichenfolge eine Übereinstimmung darstellt *

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
Mayur Ingle
quelle
3

Warum nicht reguläre Ausdrücke verwenden:

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]))
ahmadh
quelle
1
Ich würde gerne in der Lage sein, dies zu verstehen. Wo kann ich effizient herausfinden, was (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)bedeutet? Vielen Dank!
user1063287
9
Wirklich eine schlechte Idee. Überall gebrochenes HTML.
Ufoguy
2
Warum nicht reguläre Ausdrücke verwenden, um HTML zu analysieren: stackoverflow.com/questions/1732348/…
allcaps
@ user1063287, das Web ist voll von Regex-Tutorials. Es lohnt sich, ein paar zu lesen. Während REs sehr verworren sein können, ist die, nach der Sie fragen, ziemlich einfach.
Alexis
3

Links können sich innerhalb einer Vielzahl von Attributen befinden, sodass Sie eine Liste dieser Attribute zur Auswahl übergeben können

Zum Beispiel mit den Attributen src und href (hier verwende ich den Operator begin with ^, um anzugeben, dass einer dieser Attributwerte mit http beginnt. Sie können dies nach Bedarf anpassen

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)

Attribut = Wertauswahl

[attr ^ = Wert]

Stellt Elemente mit dem Attributnamen attr dar, dessen Wert vor dem Wert steht.

QHarr
quelle
1

Hier ist ein Beispiel @ars akzeptierte Antwort mit und BeautifulSoup4, requestsund wgetModule die Downloads zu handhaben .

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)
Blairg23
quelle
1

Ich fand die Antwort von @ Blairg23 nach der folgenden Korrektur (die das Szenario abdeckt, in dem es nicht richtig funktioniert hat):

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)

Für Python 3:

urllib.parse.urljoin muss verwendet werden, um stattdessen die vollständige URL zu erhalten.

AkanKsha Bhardwaj
quelle
1

Der Parser von BeatifulSoup kann langsam sein. Es ist möglicherweise praktikabler, lxml zu verwenden, das direkt von einer URL aus analysiert werden kann (mit einigen unten genannten Einschränkungen).

import lxml.html

doc = lxml.html.parse(url)

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

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

Der obige Code gibt die Links so zurück, wie sie sind, und in den meisten Fällen handelt es sich um relative oder absolute Links vom Site-Stammverzeichnis. Da mein Anwendungsfall darin bestand, nur eine bestimmte Art von Links zu extrahieren, finden Sie unten eine Version, die die Links in vollständige URLs konvertiert und optional ein Glob-Muster wie akzeptiert *.mp3. Es werden zwar keine einfachen und doppelten Punkte in den relativen Pfaden verarbeitet, aber bisher hatte ich keine Notwendigkeit dafür. Wenn Sie zu parsen URL - Fragmente enthalten ../oder ./dann urlparse.urljoin könnte sich als nützlich.

HINWEIS : Das direkte Parsen von lxml-URLs übernimmt nicht das Laden von httpsund leitet keine Weiterleitungen durch. Aus diesem Grund verwendet die folgende Version 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

Die Verwendung ist wie folgt:

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"
ccpizza
quelle
lxmlkann nur gültige Eingaben verarbeiten, wie kann es ersetzen BeautifulSoup?
Alexis
@alexis: Ich denke, lxml.htmlist ein bisschen nachsichtiger als die lxml.etree. Wenn Ihre Eingabe nicht korrekt ist, können Sie den BeautifulSoup-Parser explizit festlegen: lxml.de/elementsoup.html . Und wenn Sie sich für BeatifulSoup entscheiden, ist BS3 die bessere Wahl.
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']
Tilak Patidar
quelle
0

Es kann viele doppelte Links zusammen mit externen und internen Links geben. Um zwischen den beiden zu unterscheiden und nur eindeutige Links mithilfe von Sets zu erhalten:

# 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)
Alexander
quelle