Передача веб-данных в Beautiful Soup — пустой список

Я перепроверил свой код и рассмотрел сопоставимые операции по открытию URL-адреса для передачи веб-данных в Beautiful Soup, по какой-то причине мой код просто ничего не возвращает, хотя он в правильной форме:

>>> from bs4 import BeautifulSoup

>>> from urllib3 import poolmanager

>>> connectBuilder = poolmanager.PoolManager()

>>> content = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')

>>> content
<urllib3.response.HTTPResponse object at 0x00000000032EC390>

>>> soup = BeautifulSoup(content)

>>> soup.title
>>> soup.title.name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'NoneType' object has no attribute 'name'
>>> soup.p
>>> soup.get_text()
''

>>> content.data
a stream of data follows...

Как показано, ясно, что urlopen() возвращает HTTP-ответ, который захвачен переменным содержимым, имеет смысл, что он может прочитать статус ответа, но после того, как он будет передан в Beautiful Soup, веб-данные не будут преобразованы в объект Beautiful Soup (переменный суп). Вы можете видеть, что я пытался прочитать несколько тегов и текста, но get_text() возвращает пустой список, это странно.

Как ни странно, когда я получаю доступ к веб-данным через content.data, данные отображаются, но это бесполезно, так как я не могу использовать Beautiful Soup для их анализа. В чем моя проблема? Спасибо.


person user3885774    schedule 31.07.2014    source источник
comment
Он явно преобразуется в объект BeautifulSoup, иначе soup.title вызвал бы исключение, а не дал бы вам None. Лучший способ узнать это — распечатать type(soup).   -  person abarnert    schedule 31.07.2014
comment
ваш код ничего не получает, попробуйте распечатать content.read()   -  person Padraic Cunningham    schedule 31.07.2014
comment
Есть ли причина, по которой вы создаете пул вручную, а затем вызываете вызов самого низкого уровня для выполнения запроса на него?   -  person abarnert    schedule 31.07.2014
comment
@abarnert Понятно, спасибо.   -  person user3885774    schedule 01.08.2014
comment
@PadraicCunningham content.read() дает b''   -  person user3885774    schedule 01.08.2014
comment
b для байтов и пустой строки   -  person Padraic Cunningham    schedule 01.08.2014
comment
@abarnert Я также посмотрел этот модуль и прочитал о самом низком уровне, но я не очень хорошо его понял и подумал, что urlopen() был самым низким уровнем, поэтому я выбрал последний.   -  person user3885774    schedule 01.08.2014
comment
@ user3885774: Да, urlopen — самый низкий уровень. Если у вас нет веской причины, вы не хотите использовать самый низкий уровень. Особенно, если вы только учитесь. Вот почему в той же документации как минимум дважды рекомендуется использовать один из удобных методов. Хотя вы могли бы изучить все мельчайшие детали того, как urllib3 работает под прикрытием, не лучше ли сначала научиться использовать его простым способом и написать некоторый рабочий код, с которым можно поиграться, чтобы учиться дальше?   -  person abarnert    schedule 01.08.2014
comment
@abarnert Согласен, я плохо интерпретировал/понял примечания модуля. :)   -  person user3885774    schedule 01.08.2014


Ответы (4)


Если вы просто хотите очистить страницу, requests получит нужный вам контент:

from bs4 import BeautifulSoup

import requests
r = requests.get('http://www.crummy.com/software/BeautifulSoup/')
soup = BeautifulSoup(r.content)

In [59]: soup.title
Out[59]: <title>Beautiful Soup: We called him Tortoise because he taught us.</title>

In [60]: soup.title.name
Out[60]: 'title'
person Padraic Cunningham    schedule 31.07.2014

urllib3 возвращает объект Response, который содержит .data с предварительно загруженной полезной нагрузкой тела.

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

import urllib3
http = urllib3.PoolManager()
response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/')

from bs4 import BeautifulSoup
soup = BeautifulSoup(response.data)  # Note the use of the .data property
...

Остальное должно работать как задумано.

--

Немного о том, что пошло не так в исходном коде:

Вы передали весь объект response, а не полезную нагрузку тела. Обычно это нормально, поскольку объект response представляет собой файлоподобный объект, за исключением в этом случае urllib3 уже использует весь ответ и анализирует его для вас, так что для .read() ничего не остается. Это похоже на передачу указателя файла, который уже был прочитан. .data, с другой стороны, получит доступ к уже прочитанным данным.

Если вы хотите использовать объекты ответа urllib3 в качестве файловых объектов, вам необходимо отключить предварительную загрузку содержимого, например:

response = http.request('GET', 'http://www.crummy.com/software/BeautifulSoup/', preload_content=False)
soup = BeautifulSoup(response)  # We can pass the original `response` object now.

Теперь он должен работать так, как вы ожидали.

Я понимаю, что это не очень очевидное поведение, и как автор urllib3 приношу свои извинения. :) Мы планируем когда-нибудь сделать preload_content=False значением по умолчанию. Возможно, когда-нибудь в ближайшее время (здесь я открыл вопрос).

--

Небольшое примечание о .urlopen против .request:

.urlopen предполагает, что вы позаботитесь о кодировании любых параметров, переданных в запрос. В этом случае можно использовать .urlopen, потому что вы не передаете никаких параметров в запрос, но в целом .request сделает за вас всю дополнительную работу, так что это удобнее.

Если кто-то захочет улучшить нашу документацию в этом отношении, мы будем очень признательны. :) Пожалуйста, отправьте PR на https://github.com/shazow/urllib3 и добавьте себя в качестве участника!

person shazow    schedule 31.07.2014
comment
Я очень ценю ваши объяснения, я признаю, что понятия не имел, что такое предварительная загрузка контента в точном выражении. Я новичок в Python и связанных с ним элементах, хотя я знал, что параметры URL часто необходимы для более точных операций, я думал, что urlopen был более простым и стандартным/предпочтительным методом. :) - person user3885774; 01.08.2014
comment
Не беспокойтесь, ваш опыт будет для меня полезным отзывом. :) - person shazow; 02.08.2014

Как показано, ясно, что urlopen() возвращает HTTP-ответ, который захвачен переменным содержимым…

То, что вы назвали content, является не содержимым, а файлоподобным объектом, из которого вы можете прочитать содержимое. BeautifulSoup вполне доволен такой вещью, но распечатывать ее для целей отладки не очень полезно. Итак, давайте на самом деле прочитаем содержимое, чтобы упростить отладку:

>>> response = connectBuilder.urlopen('GET', 'http://www.crummy.com/software/BeautifulSoup/')
>>> response
<urllib3.response.HTTPResponse object at 0x00000000032EC390>
>>> content = response.read()
>>> content
b''

Это должно прояснить, что BeautifulSoup здесь не проблема. Но продолжая:

… но после того, как они переданы в Beautiful Soup, веб-данные не преобразуются в объект Beautiful Soup (переменный суп).

Да, это так. Тот факт, что soup.title дал вам None вместо повышения AttributeError, является довольно хорошим доказательством, но вы можете проверить это напрямую:

>>> type(soup)
bs4.BeautifulSoup

Это определенно BeautifulSoup объект.

Когда вы передаете BeautifulSoup пустую строку, именно то, что вы возвращаете, будет зависеть от того, какой синтаксический анализатор он использует внутри; если он полагается на стандартную библиотеку Python 3.x, вы получите узел html с пустым head и пустым body, и ничего больше. Итак, когда вы ищете узел title, его нет, и вы получаете None.


Итак, как это исправить?

Как сказано в документации, вы используете «вызов самого низкого уровня для выполнения запроса, поэтому вам нужно указать все необработанные детали». Что это за необработанные детали? Честно говоря, если вы еще не знаете, вам не следует использовать этот метод. Обучение вас тому, как обращаться с подкапотными деталями urllib3, прежде чем вы даже узнаете основы, не окажет вам услуги.

На самом деле, urllib3 здесь вообще не нужен. Просто используйте модули, которые поставляются с Python:

>>> # on Python 2.x, instead do: from urllib2 import urlopen 
>>> from urllib.request import urlopen
>>> r = urlopen('http://www.crummy.com/software/BeautifulSoup/')
>>> soup = BeautifulSoup(r)
>>> soup.title.text
'Beautiful Soup: We called him Tortoise because he taught us.'
person abarnert    schedule 31.07.2014
comment
Спасибо, но когда я попытался продолжить синтаксический анализ, я не получил ничего похожего на soap.find_all(True) и soap.get_text(), поэтому я был сбит с толку. - person user3885774; 01.08.2014
comment
@ user3885774: Вот что объясняет мой последний абзац: у вас может быть пустой суп или суп только с узлом html с пустыми head и body, но на самом деле это не имеет значения; полезных данных нет, так кого же волнует, как именно представлено это отсутствие полезных данных? - person abarnert; 01.08.2014
comment
urllib3 на самом деле возвращает объект, похожий на файл, но он используется по умолчанию (это не идеально, как я упоминал в своем ответе ниже и открыл проблему). Чтобы это исправить, используйте preload_content=False в параметре запроса. - person shazow; 01.08.2014
comment
@shazow: Или, проще говоря, просто используйте r.data, куда идет предварительно загруженный контент. Или, еще проще, не используйте urllib3, если он вам не нужен и вам слишком сложно найти в документации то, что вам нужно… - person abarnert; 01.08.2014
comment
@abarnert Или дайте отзыв автору urllib3 о том, как сделать его не слишком сложным, чтобы он мог это исправить. :) Или, что еще предпочтительнее, помогите его улучшить! - person shazow; 02.08.2014
comment
@shazow: Честно говоря, я только дважды смотрел на urllib3. Оба раза я ожидал, что requests сможет сделать что-то для меня, как по волшебству, но этого не произошло, поэтому я заглянул под одеяло, увидел, что urllib3 позволяет легко делать то, что я хотел, и написал патч, раскрывающий поведение до requests. Оба раза я не увидел ничего плохого в urllib3, поэтому реальных предложений по его улучшению у меня нет. Но я ответил на ваш # 436. - person abarnert; 03.08.2014

Мой красивый суповый код работал в одной среде (моя локальная машина) и возвращал пустой список в другой (сервер Ubuntu 14).

Я решил свою проблему, изменив установку. подробности в другой теме:

Синтаксический анализ HTML с помощью Beautiful Soup возвращает пустой список

person Roger Camargo    schedule 24.07.2015
comment
Обратите внимание, что ответы только по ссылкам не рекомендуются, ответы SO должны быть конечной точкой поиска. для решения (по сравнению с еще одной остановкой ссылок, которые со временем устаревают). Пожалуйста, рассмотрите возможность добавления здесь отдельного синопсиса, оставив ссылку в качестве ссылки. - person kleopatra; 25.07.2015