#python #html #selenium #beautifulsoup #attributeerror
#python #HTML #селен #beautifulsoup #ошибка атрибута
Вопрос:
Я использую Python 3.9.1 с selenium и BeatifulSoup, чтобы создать свой первый webscraper для веб-сайта Tesco (мини-проект для самостоятельного обучения). Однако, когда я запускаю код, как показано ниже, я получаю ошибку атрибута:
Traceback (most recent call last):
File "c:UsersOzzieDropboxMy PC (DESKTOP-HFVRPAV)DesktopTescoTesco.py", line 37, in <module>
clean_product_data = process_products(html)
File "c:UsersOzzieDropboxMy PC (DESKTOP-HFVRPAV)DesktopTescoTesco.py", line 23, in process_products
weight = product_price_weight.find("span",{"class":"weight"}).text.strip()
AttributeError: 'NoneType' object has no attribute 'find'
Я не уверен, что происходит не так — разделы title и URL работают нормально, но разделы weight и price возвращают это значение. Когда я попытался напечатать переменные product_price и product_price_weight , они вернули значения, которые я от них ожидал (я не буду публиковать это здесь, это просто очень длинный HTML).
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from webdriver_manager.chrome import ChromeDriverManager
import time
from bs4 import BeautifulSoup
driver = webdriver.Chrome(ChromeDriverManager().install())
def process_products(html):
clean_product_list = []
soup = BeautifulSoup(html, 'html.parser')
products = soup.find_all("div",{"class":"product-tile-wrapper"})
for product in products:
data_dict = {}
product_details = product.find("div",{"class":"product-details--content"})
product_price = product.find("div",{"class":"price-control-wrapper"})
product_price_weight = product.find("div",{"class":"price-per-quantity-weight"})
data_dict['title'] = product_details.find('a').text.strip()
data_dict['product_url'] = ('tesco.com') (product_details.find('a')['href'])
weight = product_price_weight.find("span",{"class":"weight"}).text.strip()
data_dict['price'] = product_price.find("span",{"class":"value"}).text.strip()
data_dict['price' weight] = product_price_weight.find("span",{"class":"value"}).text.strip()
clean_product_list.append(data_dict)
return clean_product_list
master_list = []
for i in range (1,3):
print (i)
driver.get(f"https://www.tesco.com/groceries/en-GB/shop/fresh-food/all?page={i}amp;count=48")
html = driver.page_source
driver.maximize_window()
clean_product_data = process_products(html)
master_list.extend(clean_product_data)
print (master_list)
Любая помощь очень ценится.
Большое спасибо,
Комментарии:
1. Вы выполняете ‘product.find()’ в цикле, поэтому само собой разумеется, что он может вернуть ‘None’. Вы должны проверить это перед использованием любых переменных, назначенных из результата.
2. Спасибо за предложение. Как именно вы рекомендуете мне это сделать?
3. Привет, Озгур, да! каким-то образом эта переменная yourd
process_products weight
имеет значениеNone
, и вы делаете.find()
с ней. Так что да, это действительно вызовет ошибку.4. Как вы обычно проверяете ‘None’?
5. Вы должны включить в свой вопрос хотя бы один тег продукта —
"div",{"class":"product-tile-wrapper"}
отформатированный как код. Может быть лучший способ извлечь из него информацию.
Ответ №1:
Вы можете попробовать это, обновив свою process_products
функцию. Еще раз обратите внимание, ЧТО БЫВАЮТ СЛУЧАИ, когда какая-либо ваша переменная, которую вы пытаетесь выполнить .find()
, возвращает a None
, что просто означает, что у нее НЕТ find
никакой элементной базы для параметров, заданных в вашей .find()
функции.
Пример этого:
Допустим, эта часть кода выполнена
product_details = product.find("div",{"class":"product-details--content"})
Теперь, если он найдет элемент на основе этих tags
amp; class
, он вернет bs4
объект, но если он не вернется None
, допустим, он вернулся None
.
Итак, ваша product_details
переменная будет None
объектом, поэтому, как только она None
снова появится в вашем коде, вы сделаете это. Опять product_details
же, где None
data_dict['title'] = product_details.find('a').text.strip()
#Another way of saying is
#data_dict['title'] = None.find('a').text.strip() ##Clearly an ERROR
Итак, что я сделал здесь, это поместил ее в a, try
except
чтобы просто перехватить эти ошибки и выдать вам пустые строки, указывающие, что, вероятно, ваша переменная, которую вы пытаетесь выполнить .find()
, возвращает a None
или могут быть некоторые ошибки (дело в том, что соответствующие данные не возвращаются), вот почему я использую try
except
, но вы могли бытакже просто сделайте if
else
из этого, но я думаю, что лучше делать это в a try
except
.
def process_products(html):
clean_product_list = []
soup = BeautifulSoup(html, 'html.parser')
products = soup.find_all("div",{"class":"product-tile-wrapper"})
for product in products:
data_dict = {}
product_details = product.find("div",{"class":"product-details--content"})
product_price = product.find("div",{"class":"price-control-wrapper"})
product_price_weight = product.find("div",{"class":"price-per-quantity-weight"})
try:
data_dict['title'] = product_details.find('a').text.strip()
data_dict['product_url'] = ('tesco.com') (product_details.find('a')['href'])
except BaseException as no_prod_details:
'''
This would mean that your product_details variable might be equal to None, so catching the error amp; setting
yoour data with empty strings, indicating it can't do a .find()
'''
data_dict['title'] = ''
data_dict['product_url'] = ''
try:
data_dict['price'] = product_price.find("span",{"class":"value"}).text.strip()
except BaseException as no_prod_price:
#Same here
data_dict['price'] =''
try:
weight = product_price_weight.find("span",{"class":"weight"}).text.strip()
data_dict['price' weight] = product_price_weight.find("span",{"class":"value"}).text.strip()
except BaseException as no_prod_price_weigth:
#Same here again
weight = ''
data_dict['price' weight] = ''
clean_product_list.append(data_dict)
return clean_product_list
Комментарии:
1. Почему бы просто не перехватить
AttributeError
и не выполнить одну попытку / исключение для всего набора циклов for — по крайней мере, для всех .finds — и затем войти, распечатать или передать в except набор?2. Прежде всего, с
AttributeError
да, но я не предполагаю тип ошибки, я позволю спрашивающему решить за него, что он будет указывать, конечно, указывать конкретную ошибку приятно. Во-вторых, поместите единицуtry/except
вокруг всего цикла for? Я так не думаю, потому что могут быть случаи, когда некоторые переменные могут бытьNone
, а некоторые НЕТ, что на самом деле находит текст результата … так что, если это так, это может повлиять на другие результаты, которые вообще не имеют проблем.3. Большое вам спасибо. Программа, наконец, работает. Однако я не понимаю, почему теперь, когда я добавил этот код try и except, программа работает безупречно, но в списке, который создается в конце, нет пропущенных разделов. Если ранее была ошибка и было добавлено try except , конечно, можно было бы ожидать, что некоторые данные будут отсутствовать, поскольку в первую очередь возвращалось значение none ? Я любитель в этом, просто пытаюсь понять рассуждения и логику, стоящие за этим, поскольку это правильное решение afaik.
4. @wwii Идея здесь в том, что если есть элемент, который возвращает
None
, то просто назначьте пустую строку, указывающую, что он не нашел ни одного элемента для получения текста.5. Правильно. Я думаю, что наконец-то понял, что я сделал не так. Спасибо за всю вашу помощь и время, которое вы потратили, чтобы ответить на мой запрос. Вы только что сделали мой день 🙂