recuperar links da página da web usando python e BeautifulSoup


Respostas:


193

Aqui está um pequeno trecho usando a classe SoupStrainer em 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'])

A documentação do BeautifulSoup é realmente muito boa e abrange vários cenários típicos:

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

Edit: Note que eu usei a classe SoupStrainer porque é um pouco mais eficiente (memória e velocidade), se você souber o que está analisando com antecedência.


13
+1, usar o coador de sopa é uma ótima idéia, pois permite contornar muitas análises desnecessárias quando tudo o que você procura são os links.
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."
Atenção

27
Na versão 3.2.1 do BeautifulSoup, não há has_attr. Em vez disso, vejo que há algo chamado has_keye funciona.

2
Atualização para python3
john doe

7
de bs4 import BeautifulSoup. (não da BeautifulSoup import BeautifulSoup ..) correção necessária.
Rishabh Agrahari

67

Para completar, a versão BeautifulSoup 4, usando também a codificação fornecida pelo servidor:

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 a versão do 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 uma versão usando a requestsbiblioteca , que, conforme escrita, funcionará no Python 2 e 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'])

A soup.find_all('a', href=True)chamada localiza todos os <a>elementos que possuem um hrefatributo; elementos sem o atributo são ignorados.

O BeautifulSoup 3 parou o desenvolvimento em março de 2012; novos projetos realmente devem usar o BeautifulSoup 4, sempre.

Observe que você deve deixar a decodificação do HTML dos bytes para BeautifulSoup . Você pode informar o BeautifulSoup do conjunto de caracteres encontrado nos cabeçalhos de resposta HTTP para ajudar na decodificação, mas isso pode estar errado e entrar em conflito com as <meta>informações do cabeçalho encontradas no próprio HTML, e é por isso que o acima usa o método da classe interna BeautifulSoup EncodingDetector.find_declared_encoding()para garantir que essas dicas de codificação incorporadas conquistam um servidor mal configurado.

Com requests, o response.encodingatributo padrão é Latin-1 se a resposta tiver um text/*tipo de mimet, mesmo que nenhum conjunto de caracteres tenha sido retornado. Isso é consistente com os RFCs HTTP, mas é doloroso quando usado com análise de HTML, portanto, você deve ignorar esse atributo quando nenhum charsetestiver definido no cabeçalho Tipo de Conteúdo.


Existe algo como StrainedSoup para bs4? (Eu não preciso disso agora, mas apenas querendo saber, se há que você pode querer adicionar isso)
Antti Haapala

@AnttiHaapala: SoupStrainervocê quer dizer? Não foi a lugar nenhum, ainda faz parte do projeto .
Martijn Pieters

Existe uma razão para esse código não passar "features =" para o construtor BeautifulSoup? BeautifulSoup me dá um aviso sobre o uso de um analisador padrão.
MikeB

1
@ MikeB: quando escrevi esta resposta, a BeautifulSoup ainda não emitiu um aviso, se você não o fez.
Martijn Pieters

50

Outros recomendaram o BeautifulSoup, mas é muito melhor usar o lxml . Apesar do nome, é também para analisar e copiar HTML. É muito, muito mais rápido que o BeautifulSoup, e até lida com HTML "quebrado" melhor que o BeautifulSoup (sua reivindicação à fama). Também possui uma API de compatibilidade para BeautifulSoup, se você não quiser aprender a API lxml.

Ian Blicking concorda .

Não há mais motivo para usar o BeautifulSoup, a menos que você esteja no Google App Engine ou algo em que algo que não seja puramente Python não seja permitido.

O lxml.html também suporta seletores CSS3, portanto esse tipo de coisa é trivial.

Um exemplo com lxml e xpath ficaria assim:

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
O BeautifulSoup 4 será usado lxmlcomo o analisador padrão, se instalado.
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'

Isso resolveu um problema que tive com meu código. Obrigado!
RJ

10

O código a seguir é recuperar todos os links disponíveis em uma página da Web usando 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

Sob o capô, o BeautifulSoup agora usa lxml. Pedidos, lxml e compreensão de lista fazem uma combinação incrível.

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]

Na lista comp, o "se '//' e 'url.com' não estiver em x" é um método simples para limpar a lista de URLs dos URLs de navegação 'internos' dos sites, etc.


1
Se é uma repostagem, por que a postagem original não inclui: 1. solicitações 2.list comp 3. lógica para remover links internos e indesejados do site? Experimente e compare os resultados das duas postagens, minha lista comp faz um trabalho surpreendentemente bom limpando os links indesejados.
cheekybastard

O OP não solicitou esses recursos e a parte que ele solicitou já foi publicada e resolvida usando exatamente o mesmo método que você publica. No entanto, removerei o voto negativo, pois a compreensão da lista agrega valor para as pessoas que desejam esses recursos e você os menciona explicitamente no corpo da postagem. Além disso, você poderia usar o representante :)
dotancohen

4

apenas para obter os links, sem 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]

para operações mais complexas, é claro que o BSoup ainda é o preferido.


7
E se, por exemplo, houver algo entre <ae href? Diga rel="nofollow"ou onclick="..."mesmo apenas uma nova linha? stackoverflow.com/questions/1732348/…
dimo414

existe uma maneira de filtrar apenas alguns links com isso? como dizer que eu só quero links que tenham "Episódio" no link?
nwgat

4

Esse script faz o que você procura, mas também resolve os links relativos aos links 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

Isso não faz o que você pretende fazer; se resolve_links () não tiver uma raiz, nunca retornará nenhum URL.
MikeB

4

Para encontrar todos os links, neste exemplo, usaremos o módulo urllib2 junto com o re.module * Uma das funções mais poderosas no módulo re é "re.findall ()". Enquanto re.search () é usado para encontrar a primeira correspondência para um padrão, re.findall () localiza todas as correspondências e as retorna como uma lista de cadeias, com cada cadeia representando uma correspondência *

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

Por que não usar expressões 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]))

1
Eu adoraria ser capaz de entender isso. Onde posso descobrir com eficiência o que (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)significa? obrigado!
user1063287

9
Realmente uma má ideia. HTML quebrado em todos os lugares.
precisa saber é o seguinte

2
Por que não usar expressões regulares para analisar html: stackoverflow.com/questions/1732348/…
allcaps

@ user1063287, a web está cheia de tutoriais sobre regex. Vale a pena ler um par. Enquanto os REs podem ficar realmente complicados, o que você está perguntando é bastante básico.
21416 Alexis

3

Os links podem estar em uma variedade de atributos para que você possa passar uma lista desses atributos para selecionar

por exemplo, com o atributo src e href (aqui estou usando o operador begin com ^ para especificar que um desses valores de atributos começa com http. Você pode adaptá-lo conforme necessário

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 = seletores de valor

[attr ^ = valor]

Representa elementos com um nome de atributo de attr cujo valor é prefixado (precedido) por valor.


1

Aqui está um exemplo usando @ars resposta aceita e os BeautifulSoup4, requestse wgetmódulos para lidar com os downloads.

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

Encontrei a resposta do @ Blairg23 funcionando, após a seguinte correção (cobrindo o cenário em que não funcionou corretamente):

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 deve ser usado para obter o URL completo.


1

O analisador do BeatifulSoup pode ser lento. Pode ser mais viável usar o lxml, que é capaz de analisar diretamente de uma URL (com algumas limitações mencionadas abaixo).

import lxml.html

doc = lxml.html.parse(url)

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

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

O código acima retornará os links como estão e, na maioria dos casos, seriam links relativos ou absolutos da raiz do site. Como meu caso de uso era apenas para extrair um certo tipo de links, abaixo está uma versão que converte os links em URLs completos e que opcionalmente aceita um padrão global como *.mp3. Porém, ele não suporta pontos únicos e duplos nos caminhos relativos, mas até agora eu não precisava disso. Se você precisar analisar fragmentos de URL que contenham ../ou ./então urlparse.urljoin, pode ser útil.

OBSERVAÇÃO : A análise direta de URL lxml não processa o carregamento httpse não redireciona, portanto, por esse motivo, a versão abaixo 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

O uso é o seguinte:

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"

lxmlsó pode lidar com entrada válida, como ele pode substituir BeautifulSoup?
21413 Alexis

@exex: Eu acho que lxml.htmlé um pouco mais branda do que o lxml.etree. Se sua entrada não for bem formada, você poderá definir explicitamente o analisador BeautifulSoup: lxml.de/elementsoup.html . E se você optar pelo BeatifulSoup, o BS3 é uma escolha melhor.
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

Pode haver muitos links duplicados junto com links externos e internos. Para diferenciar os dois e obter links exclusivos 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)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.