Respuestas:
Aquí hay un fragmento corto usando la clase SoupStrainer en 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 documentación de BeautifulSoup es bastante buena y cubre varios escenarios típicos:
https://www.crummy.com/software/BeautifulSoup/bs4/doc/
Editar: Tenga en cuenta que usé la clase SoupStrainer porque es un poco más eficiente (memoria y velocidad), si sabe de antemano lo que está analizando.
/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."
has_attr
. En cambio, veo que hay algo llamado has_key
y funciona.
Para completar, la versión BeautifulSoup 4, haciendo uso de la codificación proporcionada por el servidor también:
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 versión de 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']
y una versión que usa la requests
biblioteca , que tal como está escrita funcionará tanto en Python 2 como en 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)
llamada encuentra todos los <a>
elementos que tienen un href
atributo; los elementos sin el atributo se omiten.
BeautifulSoup 3 detuvo el desarrollo en marzo de 2012; los nuevos proyectos realmente deberían usar BeautifulSoup 4, siempre.
Tenga en cuenta que debe dejar la decodificación del HTML de bytes a BeautifulSoup . Puede informar a BeautifulSoup del conjunto de caracteres que se encuentra en los encabezados de respuesta HTTP para ayudar en la decodificación, pero esto puede ser incorrecto y contradictorio con una <meta>
información de encabezado que se encuentra en el propio HTML, razón por la cual lo anterior usa el método de clase interna BeautifulSoup EncodingDetector.find_declared_encoding()
para asegurarse de que Estas sugerencias de codificación incorporadas ganan a un servidor mal configurado.
Con requests
, el response.encoding
atributo predeterminado es Latin-1 si la respuesta tiene un tipo text/*
MIME, incluso si no se devuelve ningún conjunto de caracteres. Esto es coherente con los RFC de HTTP, pero es doloroso cuando se usa con el análisis HTML, por lo que debe ignorar ese atributo cuando no charset
se establece en el encabezado Content-Type.
SoupStrainer
quieres decir? No fue a ninguna parte, todavía es parte del proyecto .
Otros han recomendado BeautifulSoup, pero es mucho mejor usar lxml . A pesar de su nombre, también es para analizar y raspar HTML. Es mucho, mucho más rápido que BeautifulSoup, e incluso maneja HTML "roto" mejor que BeautifulSoup (su reclamo de fama). También tiene una API de compatibilidad para BeautifulSoup si no desea aprender la API lxml.
Ian Blicking está de acuerdo .
Ya no hay ninguna razón para usar BeautifulSoup, a menos que esté en Google App Engine o algo en lo que no se permita nada que no sea Python.
lxml.html también admite 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
lxml
como el analizador predeterminado si está instalado.
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 siguiente código es para recuperar todos los enlaces disponibles en una página web usando 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 usa lxml. Solicitudes, lxml y comprensiones de listas hacen una combinación asesina
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 compilación, "if '//' y 'url.com' no están en x" es un método simple para eliminar la lista de url de las URL de navegación 'internas' del sitio, etc.
solo para obtener los enlaces, sin B.soup y 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]
para operaciones más complejas, por supuesto, BSoup sigue siendo preferido.
<a
y href
? ¿Decir rel="nofollow"
o onclick="..."
incluso una nueva línea? stackoverflow.com/questions/1732348/…
Este script hace lo que buscas, pero también resuelve los enlaces relativos a enlaces 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
Para encontrar todos los enlaces, en este ejemplo usaremos el módulo urllib2 junto con el re.module * Una de las funciones más poderosas en el módulo re es "re.findall ()". Mientras que re.search () se usa para encontrar la primera coincidencia de un patrón, re.findall () encuentra todas las coincidencias y las devuelve como una lista de cadenas, y cada cadena representa una coincidencia *
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
¿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]))
(r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)
significa? ¡Gracias!
Los enlaces pueden estar dentro de una variedad de atributos para que pueda pasar una lista de esos atributos para seleccionar
por ejemplo, con el atributo src y href (aquí estoy usando el operador comienza con ^ para especificar que cualquiera de estos valores de atributos comience con http. Puede personalizar esto según 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 = selectores de valor
[attr ^ = valor]
Representa elementos con un nombre de atributo de atributo cuyo valor está prefijado (precedido) por valor.
He aquí un ejemplo usando @ars respuesta aceptada y los BeautifulSoup4
, requests
y wget
módulos para manejar las descargas.
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)
Encontré la respuesta de @ Blairg23 funcionando, después de la siguiente corrección (cubriendo el escenario donde 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)
Para Python 3:
urllib.parse.urljoin
tiene que usarse para obtener la URL completa en su lugar.
El propio analizador de BeatifulSoup puede ser lento. Puede ser más factible usar lxml, que es capaz de analizar directamente desde una URL (con algunas limitaciones que se 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 devolverá los enlaces tal como están, y en la mayoría de los casos serían enlaces relativos o absolutos de la raíz del sitio. Dado que mi caso de uso era solo extraer un cierto tipo de enlaces, a continuación hay una versión que convierte los enlaces a URL completos y que opcionalmente acepta un patrón global como *.mp3
. Sin embargo, no manejará puntos simples y dobles en las rutas relativas, pero hasta ahora no tuve la necesidad de hacerlo. Si necesita analizar fragmentos de URL que contengan ../
o ./
luego, urlparse.urljoin puede ser útil.
NOTA : El análisis directo de URL de lxml no maneja la carga https
y no realiza redireccionamientos, por lo que por esta razón la siguiente versión está usando 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 el siguiente:
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"
lxml
solo puede manejar una entrada válida, ¿cómo puede reemplazarla BeautifulSoup
?
lxml.html
es un poco más indulgente que el lxml.etree
. Si su entrada no está bien formada, puede establecer explícitamente el analizador BeautifulSoup: lxml.de/elementsoup.html . Y si vas con BeatifulSoup, entonces BS3 es una mejor opción.
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']
Puede haber muchos enlaces duplicados junto con enlaces externos e internos. Para diferenciar entre los dos y simplemente obtener enlaces únicos usando conjuntos:
# 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)