извлекать ссылки с веб-страницы с помощью python и BeautifulSoup

StackOverflow https://stackoverflow.com/questions/1080411

Вопрос

Как я могу получить ссылки на веб-страницу и скопировать URL-адрес ссылок с помощью Python?

Это было полезно?

Решение

Вот короткий фрагмент с использованием класса SoupStrainer в 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'])

Документация BeautifulSoup на самом деле довольно хороша и охватывает ряд типичных сценариев:

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

Редактировать:Обратите внимание, что я использовал класс SoupStrainer, потому что он немного эффективнее (с точки зрения памяти и скорости), если вы заранее знаете, что анализируете.

Другие советы

Для полноты картины, версия BeautifulSoup 4, также использующая кодировку, предоставляемую сервером:

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

или версия 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'])

и версия, использующая requests библиотека, который , как написано , будет работать как в Python 2 , так и в 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'])

В soup.find_all('a', href=True) вызов находит все <a> элементы , которые имеют href атрибут;элементы без этого атрибута пропускаются.

BeautifulSoup 3 прекратил разработку в марте 2012 года;новые проекты действительно должны всегда использовать BeautifulSoup 4.

Обратите внимание, что вам следует оставить декодирование HTML из байтов к прекрасному супу.Вы можете сообщить BeautifulSoup о наборе символов, найденном в заголовках HTTP-ответа, чтобы помочь в декодировании, но это может ошибаться и вступать в конфликт с <meta> информация о заголовке содержится в самом HTML, именно поэтому в приведенном выше примере используется метод внутреннего класса BeautifulSoup EncodingDetector.find_declared_encoding() чтобы убедиться, что такие встроенные подсказки кодирования побеждают неправильно сконфигурированный сервер.

С requests, тот response.encoding атрибут по умолчанию имеет значение Latin-1, если ответ содержит text/* mimetype, даже если набор символов не был возвращен.Это согласуется с HTTP RFC, но вызывает дискомфорт при использовании с синтаксическим анализом HTML, поэтому вам следует игнорировать этот атрибут, когда нет charset задается в заголовке Content-Type.

Другие рекомендовали BeautifulSoup, но его гораздо лучше использовать lxml.Несмотря на свое название, он также предназначен для синтаксического анализа и очистки HTML.Это намного, намного быстрее, чем BeautifulSoup, и он даже обрабатывает "сломанный" HTML лучше, чем BeautifulSoup (их претензия на славу).В нем также есть API совместимости для BeautifulSoup, если вы не хотите изучать lxml API.

Йен Бликинг соглашается.

Больше нет причин использовать BeautifulSoup, если только вы не используете Google App Engine или что-то еще, где запрещено что-либо, кроме Python.

lxml.html также поддерживает селекторы CSS3, так что подобные вещи тривиальны.

Пример с lxml и xpath будет выглядеть следующим образом:

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'

Следующий код предназначен для извлечения всех ссылок, доступных на веб-странице, с помощью urllib2 и 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'))

Под капотом BeautifulSoup теперь использует lxml.Запросы, понимание lxml и списков составляют убийственную комбинацию.

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]

В компоненте списка "if '//' и 'url.com ' не в x" - это простой метод очистки списка URL от "внутренних" URL-адресов навигации сайтов и т.д.

Чтобы найти все ссылки, мы будем в этом примере использовать модуль urllib2 вместе с re.module * Одной из самых мощных функций в модуле re является "re.findall()".В то время как re.search() используется для поиска первого соответствия шаблону, re.findall() находит ВСЕ совпадения и возвращает их в виде списка строк, причем каждая строка представляет одно совпадение*

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

просто для получения ссылок, без B.soup и регулярных выражений:

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]

для более сложных операций, конечно, по-прежнему предпочтителен BSoup.

Почему бы не использовать регулярные выражения:

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

Этот скрипт делает то, что вы ищете, но также преобразует относительные ссылки в абсолютные.

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

Ссылки могут находиться внутри различных атрибутов, поэтому вы можете передать список этих атрибутов для выбора

например, с помощью атрибута src и href (здесь я использую оператор starts with ^, чтобы указать, что любое из значений этих атрибутов начинается с http.Вы можете адаптировать это по мере необходимости

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)

Селекторы атрибутов = значений

[attr^=значение]

Представляет элементы с именем атрибута attr, значение которого имеет префикс (перед которым) value.

Вот пример использования принятого ответа @ars и BeautifulSoup4, requests, и wget модули для обработки загрузок.

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 работает, после следующего исправления (охватывающего сценарий, в котором он не сработал корректно):

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)

Для Python 3:

urllib.parse.urljoin должен быть использован для того, чтобы вместо этого получить полный URL-адрес.

Собственный анализатор BeatifulSoup может быть медленным.Возможно, было бы более целесообразно использовать lxml который способен выполнять синтаксический анализ непосредственно с URL-адреса (с некоторыми ограничениями, упомянутыми ниже).

import lxml.html

doc = lxml.html.parse(url)

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

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

Приведенный выше код вернет ссылки как есть, и в большинстве случаев это будут относительные ссылки или абсолютные ссылки из корневого каталога сайта.Поскольку мой вариант использования заключался в извлечении ссылок только определенного типа, ниже приведена версия, которая преобразует ссылки в полные URL-адреса и которая необязательно принимает шаблон glob, такой как *.mp3.Однако он не будет обрабатывать одиночные и двойные точки в относительных путях, но до сих пор у меня не было в этом необходимости.Если вам нужно разобрать фрагменты URL, содержащие ../ или ./ тогда urlparse.urljoin -url-адрес может пригодиться.

ПРИМЕЧАНИЕ:Прямой синтаксический анализ URL-адреса lxml не обрабатывает загрузку с https и не выполняет перенаправления, поэтому по этой причине приведенная ниже версия использует 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

Использование заключается в следующем:

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']
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top