Почему я получаю ошибку атрибута для моего кода BeautifulSoup, когда рассматриваемая переменная имеет значение?

#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. Правильно. Я думаю, что наконец-то понял, что я сделал не так. Спасибо за всю вашу помощь и время, которое вы потратили, чтобы ответить на мой запрос. Вы только что сделали мой день 🙂