Pregunta

¿Cómo puedo recuperar los enlaces de una página web y copiar la dirección URL de los enlaces usando Python?

¿Fue útil?

Solución

He aquí un breve fragmento utilizando la clase SoupStrainer en BeautifulSoup:

import httplib2
from BeautifulSoup 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 documentación BeautifulSoup es bastante bueno, y abarca una serie de escenarios típicos:

http://www.crummy.com/software/BeautifulSoup/documentation.html

Editar:. Tenga en cuenta que he utilizado la clase SoupStrainer porque es un poco más eficiente (memoria y la velocidad del reloj), si sabes lo que estás analizar de antemano

Otros consejos

En aras integridad, la versión BeautifulSoup 4, haciendo uso de la codificación suministrada por el servidor, así:

from bs4 import BeautifulSoup
import urllib2

resp = urllib2.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, from_encoding=resp.info().getparam('charset'))

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

o la versión Python 3:

from bs4 import BeautifulSoup
import urllib.request

resp = urllib.request.urlopen("http://www.gpsbasecamp.com/national-parks")
soup = BeautifulSoup(resp, from_encoding=resp.info().get_param('charset'))

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

y una versión usando el requests biblioteca, que como está escrito funcionará en tanto Python 2 y 3:

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

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, from_encoding=encoding)

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

La llamada soup.find_all('a', href=True) encuentra todos los elementos <a> que tienen un atributo href; elementos sin el atributo se omiten.

3 BeautifulSoup detuvo el desarrollo en marzo de 2012; nuevos proyectos realmente debe utilizar BeautifulSoup 4, siempre.

Tenga en cuenta que debe dejar decodificar el código HTML de bytes a BeautifulSoup . Puede informar a BeautifulSoup del juego de caracteres que se encuentra en las cabeceras de respuesta HTTP para ayudar en la decodificación, pero esto puede estar mal y en oposición con una información de la cabecera <meta> encontrado en el propio HTML, por lo que los usos anteriores, la BeautifulSoup EncodingDetector.find_declared_encoding() método de la clase interna para asegurarse de que estos indicios de codificación integrados, ganan más de un servidor mal configurado.

Con requests, la response.encoding atribuir a los valores predeterminados América-1 si la respuesta tiene un tipo MIME text/*, incluso si se devuelve ningún juego de caracteres. Esto es consistente con el RFC HTTP pero dolorosa cuando se utiliza con análisis de HTML, por lo que debe pasar por alto ese atributo cuando no hay charset se encuentra en la cabecera Content-Type.

Otros han recomendado BeautifulSoup, pero es mucho mejor usar lxml . A pesar de su nombre, es también para analizar y raspando HTML. Es mucho, mucho más rápido que BeautifulSoup, e incluso se encarga de "roto" HTML mejor que BeautifulSoup (su pretensión de fama). Tiene una API de compatibilidad para BeautifulSoup también si usted no quiere aprender la API lxml.

Ian Blicking está de acuerdo .

No hay razón para usar BeautifulSoup más, a menos que esté en Google App Engine o algo donde todo lo que no puramente no está permitido Python.

lxml.html también es compatible con los selectores CSS3 por lo que este tipo de cosas es trivial.

Un ejemplo con lxml y XPath se vería así:

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

El código siguiente es para recuperar todos los enlaces disponibles en una página web utilizando urllib2 y 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'))

Bajo el capó BeautifulSoup ahora utiliza lxml. Las solicitudes, LXML y listas por comprensión hace un combo asesino.

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]

En la lista de compensación, el "si '//' y 'url.com' no en x" es un método simple para fregar la lista de direcciones URL de URLs 'internos' de navegación a los sitios, etc.

Para encontrar todos los enlaces, vamos a utilizar en este ejemplo el módulo urllib2 juntos con el re.module * Una de las funciones más poderoso del módulo re es "re.findall ()". Mientras re.search () se utiliza para encontrar el primer partido para un patrón, re.findall () encuentra todos los partidos y los devuelve como una lista de cadenas, donde cada cadena que representa un partido *

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

sólo para conseguir los enlaces, sin B.soup y expresiones regulares:

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]

para operaciones más complejas, por supuesto, es todavía preferible BSoup.

¿Por qué no usar expresiones regulares:

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

Este script hace lo que busca, sino que además resuelve los enlaces relativos a vínculos absolutos.

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

Los enlaces pueden estar dentro de una variedad de atributos por lo que podría pasar una lista de esos atributos para seleccionar

Por ejemplo, la src y atributo href (en este caso estoy usando las aperturas con ^ operador para especificar que cualquiera de estos valores de atributos comienza por http. Puede adaptar esta como sea necesario

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)

Atributo = Valor selectores

  

[attr ^ = valor]

     

representa elementos con un nombre de atributo de attr cuyo valor se prefija (precedido) por valor.

Este es un ejemplo usando @ars aceptado las BeautifulSoup4, requests y módulos wget para manejar las descargas respuesta y.

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)

He encontrado la respuesta por @ Blairg23 de trabajo, después de la siguiente corrección (que cubre el escenario en el que no funcionó correctamente):

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)

En Python 3:

urllib.parse.urljoin ha de ser utilizado con el fin de obtener la URL completa en su lugar.

propio analizador de BeatifulSoup puede ser lento. Podría ser más factible utilizar lxml que es capaz de analizar directamente de un URL ( con algunas limitaciones mencionan a continuación).

import lxml.html

doc = lxml.html.parse(url)

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

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

El código anterior devuelve los enlaces como es, y en la mayoría de los casos sería enlaces relativos o absolutos de la raíz del sitio. Desde mi caso de uso sólo para extraer un cierto tipo de enlaces, a continuación es una versión que convierte los enlaces a direcciones URL completo y que acepta opcionalmente un patrón glob como *.mp3. No va a manejar los puntos individuales y dobles en las rutas relativas, sin embargo, pero hasta ahora no tenía la necesidad de hacerlo. Si necesita analizar fragmentos de URL que contiene ../ o ./ continuación, urlparse.urljoin podría venir muy bien.

Nota: . Directo análisis lxml url no maneja la carga de https y no hace redirecciones, por lo que por esta razón la versión más adelante está utilizando 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

El uso es como sigue:

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"
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']
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top