récupérer des liens de la page Web en utilisant python et BeautifulSoup


Réponses:


193

Voici un court extrait utilisant la classe SoupStrainer dans 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 documentation BeautifulSoup est en fait assez bonne et couvre un certain nombre de scénarios typiques:

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

Edit: Notez que j'ai utilisé la classe SoupStrainer car elle est un peu plus efficace (mémoire et vitesse), si vous savez ce que vous analysez à l'avance.


13
+1, l'utilisation de la passoire à soupe est une excellente idée car elle vous permet de contourner beaucoup d'analyses inutiles lorsque vous ne recherchez que les liens.
Evan Fosmark

4
/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."
Attention

28
Sur la version 3.2.1 de BeautifulSoup, il n'y a pas de has_attr. Au lieu de cela, je vois qu'il y a quelque chose qui s'appelle has_keyet cela fonctionne.

2
Mise à jour pour Python3
John Doe

7
depuis bs4 importer BeautifulSoup. (pas de BeautifulSoup import BeautifulSoup ..) correction nécessaire.
Rishabh Agrahari

67

Par souci d'exhaustivité, la version BeautifulSoup 4, utilisant également l'encodage fourni par le serveur:

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

ou la version 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']

et une version utilisant la requestsbibliothèque , qui, telle qu'elle est écrite, fonctionnera à la fois en Python 2 et 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'])

le soup.find_all('a', href=True) appel trouve tous les <a>éléments qui ont un hrefattribut; les éléments sans l'attribut sont ignorés.

BeautifulSoup 3 a arrêté le développement en mars 2012; les nouveaux projets devraient vraiment utiliser BeautifulSoup 4, toujours.

Notez que vous devez laisser le décodage du HTML d'octets vers BeautifulSoup . Vous pouvez informer BeautifulSoup du jeu de caractères trouvé dans les en-têtes de réponse HTTP pour aider au décodage, mais cela peut être erroné et en conflit avec une <meta>information d'en-tête trouvée dans le HTML lui-même, c'est pourquoi ce qui précède utilise la méthode de classe interne de BeautifulSoupEncodingDetector.find_declared_encoding() pour vous assurer que ces astuces d'encodage intégrées l'emportent sur un serveur mal configuré.

Avec requests, l' response.encodingattribut prend par défaut Latin-1 si la réponse a un text/*type MIME, même si aucun jeu de caractères n'a été renvoyé. Ceci est cohérent avec les RFC HTTP mais pénible lorsqu'il est utilisé avec l'analyse HTML, vous devez donc ignorer cet attribut lorsque no charsetest défini dans l'en-tête Content-Type.


Existe-t-il quelque chose comme StrainedSoup pour bs4? (Je n'en ai pas besoin maintenant, mais je me demande simplement, s'il y en a, vous voudrez peut-être ajouter cela)
Antti Haapala

@AnttiHaapala: SoupStrainertu veux dire? Il n'est allé nulle part, il fait toujours partie du projet .
Martijn Pieters

Existe-t-il une raison pour laquelle ce code ne transmet pas "features =" au constructeur BeautifulSoup? BeautifulSoup me donne un avertissement sur l'utilisation d'un analyseur par défaut.
MikeB

1
@MikeB: quand j'ai écrit cette réponse, BeautifulSoup n'a pas encore émis d'avertissement si vous ne l'avez pas fait.
Martijn Pieters

50

D'autres ont recommandé BeautifulSoup, mais il est préférable d'utiliser lxml . Malgré son nom, il sert également à analyser et à gratter du HTML. C'est beaucoup, beaucoup plus rapide que BeautifulSoup, et il gère même mieux le HTML "cassé" que BeautifulSoup (leur prétention à la renommée). Il dispose également d'une API de compatibilité pour BeautifulSoup si vous ne souhaitez pas apprendre l'API lxml.

Ian Blicking est d'accord .

Il n'y a plus de raison d'utiliser BeautifulSoup, sauf si vous êtes sur Google App Engine ou quelque chose où tout ce qui n'est pas purement Python n'est pas autorisé.

lxml.html supporte également les sélecteurs CSS3, donc ce genre de chose est trivial.

Un exemple avec lxml et xpath ressemblerait à ceci:

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 utilisera lxmlcomme analyseur par défaut s'il est installé.
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'

Cela a résolu un problème que j'avais avec mon code. Je vous remercie!
RJ

10

Le code suivant permet de récupérer tous les liens disponibles dans une page Web à l'aide de urllib2et 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

Sous le capot, BeautifulSoup utilise désormais lxml. Requests, lxml & list comprehensions fait un combo tueur.

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]

Dans la liste comp, le "if '//' et 'url.com' not in x" est une méthode simple pour parcourir la liste des URL de navigation des sites 'internes', etc.


1
S'il s'agit d'un republication, pourquoi le message original n'inclut-il pas: 1. demandes 2. liste comp 3. logique pour nettoyer les liens internes et indésirables du site ?? Essayez de comparer les résultats des deux articles, ma liste de compilation fait un travail étonnamment bon en nettoyant les liens indésirables.
cheekybastard

Le PO n'a pas demandé ces fonctionnalités et la partie qu'il a demandée a déjà été publiée et résolue en utilisant exactement la même méthode que vous avez publiée. Cependant, je supprimerai le vote défavorable car la compréhension de la liste ajoute de la valeur pour les personnes qui souhaitent ces fonctionnalités et que vous les mentionnez explicitement dans le corps du message. Aussi, vous pouvez utiliser le représentant :)
dotancohen

4

juste pour obtenir les liens, sans B.soup et 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]

pour des opérations plus complexes, BSoup est bien entendu toujours préféré.


7
Et si, par exemple, il y a quelque chose entre <aet href? Dites rel="nofollow"ou onclick="..."ou même juste une nouvelle ligne? stackoverflow.com/questions/1732348/…
dimo414

existe-t-il un moyen de filtrer uniquement certains liens avec cela? comme dire que je ne veux que des liens qui ont "Episode" dans le lien?
nwgat

4

Ce script fait ce que vous recherchez, mais résout également les liens relatifs vers des liens absolus.

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

Cela ne fait pas ce qu'il est censé faire; si resolver_links () n'a pas de racine, alors il ne renvoie jamais d'URL.
MikeB

4

Pour trouver tous les liens, nous allons dans cet exemple utiliser le module urllib2 avec le re.module * Une des fonctions les plus puissantes du module re est "re.findall ()". Alors que re.search () est utilisé pour trouver la première correspondance d'un modèle, re.findall () trouve toutes les correspondances et les renvoie sous forme de liste de chaînes, chaque chaîne représentant une correspondance *

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

Pourquoi ne pas utiliser des expressions régulières:

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
J'aimerais être en mesure de comprendre cela, où puis-je trouver efficacement ce que (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)signifie? Merci!
user1063287

9
Vraiment une mauvaise idée. HTML cassé partout.
Ufoguy

2
Pourquoi ne pas utiliser des expressions régulières pour analyser le HTML: stackoverflow.com/questions/1732348
...

@ user1063287, le Web regorge de didacticiels regex. Cela vaut bien votre temps pour en lire quelques-uns. Alors que les ER peuvent être très compliqués, celui que vous demandez est assez basique.
alexis

3

Les liens peuvent être dans une variété d'attributs afin que vous puissiez passer une liste de ces attributs à sélectionner

par exemple, avec l'attribut src et href (ici, j'utilise l'opérateur commence par ^ pour spécifier que l'une ou l'autre de ces valeurs d'attribut commence par http. Vous pouvez personnaliser cela si nécessaire

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 = sélecteurs de valeur

[attr ^ = valeur]

Représente les éléments avec un nom d'attribut attr dont la valeur est précédée (précédée) de value.


1

Voici un exemple d' utilisation @ars réponse acceptée et les BeautifulSoup4, requestset wgetmodules pour gérer les téléchargements.

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

J'ai trouvé la réponse de @ Blairg23 fonctionnant, après la correction suivante (couvrant le scénario où cela ne fonctionnait pas correctement):

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)

Pour Python 3:

urllib.parse.urljoin doit être utilisé pour obtenir l'URL complète à la place.


1

Le propre analyseur de BeatifulSoup peut être lent. Il pourrait être plus possible d'utiliser lxml qui est capable d'analyser directement à partir d'une URL (avec certaines limitations mentionnées ci-dessous).

import lxml.html

doc = lxml.html.parse(url)

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

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

Le code ci-dessus renverra les liens tels quels, et dans la plupart des cas, il s'agirait de liens relatifs ou absolus de la racine du site. Comme mon cas d'utilisation consistait à extraire uniquement un certain type de liens, voici une version qui convertit les liens en URL complètes et qui accepte éventuellement un modèle global comme *.mp3. Cependant, il ne gérera pas les points simples et doubles dans les chemins relatifs, mais jusqu'à présent, je n'en avais pas besoin. Si vous avez besoin d'analyser des fragments d'URL contenant ../ou ./alors urlparse.urljoin peut être utile.

REMARQUE : L'analyse directe d'url lxml ne gère pas le chargement à partir de httpset ne fait pas de redirections, c'est pourquoi la version ci-dessous utilise 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'utilisation est la suivante:

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"

lxmlne peut gérer qu'une entrée valide, comment peut-il remplacer BeautifulSoup?
alexis

@alexis: Je pense que lxml.htmlc'est un peu plus indulgent que le lxml.etree. Si votre entrée n'est pas bien formée, vous pouvez définir explicitement l'analyseur BeautifulSoup: lxml.de/elementsoup.html . Et si vous optez pour BeatifulSoup, BS3 est un meilleur choix.
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

Il peut y avoir de nombreux liens en double avec des liens externes et internes. Pour différencier les deux et obtenir simplement des liens uniques à l'aide d'ensembles:

# 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)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.