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

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


person NepUS    schedule 03.07.2009    source источник
comment
Вот обновленный фрагмент кода, который делает именно то, что вы просите, в 30 строках. github.com/mujeebishaque/extract-urls   -  person Mujeeb Ishaque    schedule 25.04.2021
comment
Я попробовал это для ссылки и получил такие результаты, как this/info-service/downloads/#unserekataloge'. Нет возможности получить полную доступную ссылку? а не только часть дополнительной ссылки? Я хочу получить ссылки на все PDF-файлы, доступные на сайте @MujeebIshaque   -  person x89    schedule 01.07.2021


Ответы (15)


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

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

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

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

person ars    schedule 03.07.2009
comment
+1, использование ситечка для супа - отличная идея, потому что оно позволяет вам избежать ненужного синтаксического анализа, когда все, что вам нужно, - это ссылки. - person Evan Fosmark; 03.07.2009
comment
Я отредактировал, чтобы добавить подобное объяснение, прежде чем увидел комментарий Эвана. Тем не менее, спасибо, что это заметили! - person ars; 03.07.2009
comment
спасибо, это решило мою проблему, на этом я заканчиваю свой проект спасибо большое - person NepUS; 04.07.2009
comment
Внимание: /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." - person BenDundee; 19.02.2013
comment
В BeautifulSoup версии 3.2.1 нет has_attr. Вместо этого я вижу, что есть что-то под названием has_key, и это работает. - person ; 27.10.2013
comment
@NeoVe, вы можете просто использовать hasattr, встроенный Python: hasattr(link, "href") - person cat; 25.03.2016
comment
Обновление для python3 - person john doe; 06.04.2017
comment
из bs4 импортируйте BeautifulSoup. (не из BeautifulSoup import BeautifulSoup ..) требуется исправление. - person Rishabh Agrahari; 11.05.2017
comment
Результат: AttributeError: объект Doctype не имеет атрибута has_attr - person zabop; 31.10.2020

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

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

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

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

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

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

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

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

person Martijn Pieters    schedule 22.03.2014
comment
Есть ли что-нибудь вроде StrainedSoup для bs4? (Мне это сейчас не нужно, но мне просто интересно, если бы вы могли добавить это) - person Antti Haapala; 02.02.2017
comment
@AnttiHaapala: SoupStrainer ты имеешь в виду? Он никуда не делся, он все еще является частью проекта. - person Martijn Pieters; 02.02.2017
comment
Есть ли причина, по которой этот код не передает features = конструктору BeautifulSoup? BeautifulSoup выдает предупреждение об использовании парсера по умолчанию. - person MikeB; 12.05.2020
comment
@MikeB: когда я писал этот ответ, BeautifulSoup еще не вызывал предупреждения, если вы этого не сделали. - person Martijn Pieters; 18.05.2020

Другие рекомендовали 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
person aehlke    schedule 03.08.2009
comment
BeautifulSoup 4 будет использовать lxml в качестве анализатора по умолчанию, если он установлен. - person Martijn Pieters; 28.12.2014

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

Под капотом 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' not in x" - это простой способ очистить список URL-адресов «внутренних» навигационных URL-адресов сайтов и т. Д.

person cheekybastard    schedule 07.10.2013
comment
Если это репост, то почему исходный пост не включает: 1. запросы 2. составление списка 3. логику очистки внутренних и нежелательных ссылок на сайте ?? Попробуйте сравнить результаты двух сообщений, мой список списков на удивление хорошо очищает ненужные ссылки. - person cheekybastard; 16.12.2013
comment
OP не запрашивал эти функции, и часть, которую он просил, уже была опубликована и решена с использованием того же метода, что и вы. Тем не менее, я удалю отрицательный голос, поскольку понимание списка действительно увеличивает ценность для людей, которым действительно нужны эти функции, и вы явно упоминаете их в теле сообщения. Кроме того, вы можете использовать репу :) - person dotancohen; 16.12.2013

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

например, с атрибутом src и href (здесь я использую оператор, начинающийся с ^, чтобы указать, что любое из этих значений атрибутов начинается с 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)

Attribute = value selectors

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

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

person QHarr    schedule 10.04.2019

просто для получения ссылок, без 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.

person ghostdog74    schedule 04.07.2009
comment
А если, например, есть что-то между <a и href? Скажите rel="nofollow" или onclick="..." или даже просто новую строку? stackoverflow.com/questions/1732348/ - person dimo414; 13.09.2012
comment
есть ли способ отфильтровать только некоторые ссылки с этим? например, мне нужны только ссылки, в которых есть эпизод? - person nwgat; 25.04.2017

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

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
person Ricky Wilson    schedule 21.01.2015
comment
Это не делает то, что я должен делать; если resolve_links () не имеет корня, он никогда не возвращает никаких URL. - person MikeB; 12.05.2020

Чтобы найти все ссылки, в этом примере мы будем использовать модуль urllib2 вместе с re.module *. Одна из самых мощных функций в модуле re - "re. найти все()". В то время как 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
person Mayur Ingle    schedule 06.08.2015

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

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]))
person ahmadh    schedule 27.05.2012
comment
я хотел бы понять это, где я могу эффективно узнать, что означает (r"<a.*?\s*href=\"(.*?)\".*?>(.*?)</a>", page)? Благодарность! - person user1063287; 06.04.2013
comment
Действительно плохая идея. Повсюду сломанный HTML. - person Ufoguy; 19.01.2014
comment
Почему бы не использовать регулярные выражения для синтаксического анализа HTML: stackoverflow.com/questions/1732348/ - person allcaps; 18.03.2014
comment
@ user1063287, в сети полно руководств по регулярным выражениям. Стоит потратить время на то, чтобы прочитать парочку. Хотя RE могут быть очень запутанными, вопрос, о котором вы спрашиваете, довольно прост. - person alexis; 11.06.2016

Вот пример использования принятого ответа @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)
person Blairg23    schedule 11.07.2016

Я нашел ответ от @ 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.

person AkanKsha Bhardwaj    schedule 25.05.2017

Собственный синтаксический анализатор 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-адреса и при необходимости принимает глобальный шаблон, например *.mp3. Однако он не обрабатывает одиночные и двойные точки в относительных путях, но пока в этом мне не было необходимости. Если вам нужно проанализировать фрагменты URL, содержащие ../ или ./, тогда urlparse.urljoin может пригодиться.

ПРИМЕЧАНИЕ. Прямой анализ 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"
person ccpizza    schedule 10.06.2016
comment
lxml может обрабатывать только действительный ввод, как он может заменить BeautifulSoup? - person alexis; 11.06.2016
comment
@alexis: Я думаю, что lxml.html немного снисходительнее, чем lxml.etree. Если ваш ввод неверно сформирован, вы можете явно установить парсер BeautifulSoup: lxml.de/elementsoup.html. А если вы выберете BeatifulSoup, то BS3 - лучший выбор. - person ccpizza; 11.06.2016

Может быть много повторяющихся ссылок вместе как с внешними, так и с внутренними ссылками. Чтобы различать их и просто получить уникальные ссылки с помощью наборов:

# 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)
person Alexander    schedule 10.10.2019

person    schedule
comment
Это решило мою проблему с кодом. Спасибо! - person R J; 15.08.2018