Не удается использовать функцию get_text() BeautifulSoup для тега span, возвращает ошибку атрибута

#python #web-scraping #beautifulsoup

#python #очистка веб-страниц #beautifulsoup

Вопрос:

для моего курса компьютерных наук я пытаюсь написать скрипт веб-скребка на python, который находит все игры в playstation Store, которые продаются с использованием python и beautiful soup. Прямо сейчас я просто пытаюсь заставить программу перечислить все игры на первой странице, их цены и процент продаж (если он есть). Однако для всех игр, которые продаются, терминал возвращает ошибку атрибута: объект ‘nontype’ не имеет атрибута ‘get_text’. Вот мой код:

 from urllib.request import urlopen as uReq
from bs4 import BeautifulSoup as soup

my_url = 'https://store.playstation.com/en-ca/category/85448d87-aa7b-4318-9997-7d25f4d275a4/1'

uClient = uReq(my_url)
page_html = uClient.read()
uClient.close()

page_soup = soup(page_html, "html.parser")
containers = page_soup.find_all("section",{"class":"ems-sdk-product-tile__details"})



for container in containers: 

   title = container.span.get_text() 

   salePercentContainer = container.find("span",{"class":"psw-body-2 discount-badge discount-badge-- 
   undefined"})
   salePercent = salePercentContainer.get_text()
   if salePercent is None:
      salePercent = 'none'


priceContainer = container.strike
price = priceContainer#.text
if price is None:
    Rprice = container.find_all("span",{"class":"price"})
    price = Rprice[0].text

print("title: "   title)
print("sale percent: "   str(salePercent))
print("price: "   str(price))
 

Комментарии:

1. Без дополнительных подробностей все, что я могу вам точно сказать, это то, что один из объектов, с которыми вы вызываете .get_text() , не то, что вы думаете. Фактически это null (или NoneType в python). Я бы рекомендовал посмотреть container salePercentContainer , и т.д. Один из них ни к чему не приводит. Скорее всего, это salePercentContainer, и вы, вероятно, захотите проверить, равно ли оно null, прежде чем пытаться сделать get_text

2. nontype означает None , и это означает, что не удалось найти элемент на странице — поэтому вы пытаетесь сделать None.get_text()

3. вероятно, страница использует JavaScript для добавления элементов, но BeatifulSoup / requests не может запустить JavaScript. Возможно, вам понадобится Selenium для управления реальным веб-браузером, который может запускать JavaScript. Кстати: отключите JavaScript в веб-браузере и снова загрузите страницу, чтобы посмотреть, что Beautifulsoup может получить с сервера.

4. если pages работает без JavaScript, вам следует проверить, что вы получаете в page_html — ie. используйте print() или сохраните в файле и откройте в веб-браузере. Возможно, сервер распознал, что вы используете скрипт, и отправил HTML с предупреждением о ботах или с капчей.

5. @DoloMike выясняет, что значение salePercentContainer равно null, спасибо.

Ответ №1:

Введите a try:except , чтобы он не сработал, для elements которого нет того, что вы ищете.

 from urllib.request import urlopen as uReq
from bs4 import BeautifulSoup as soup

my_url = 'https://store.playstation.com/en-ca/category/85448d87-aa7b-4318-9997-7d25f4d275a4/1'

uClient = uReq(my_url)
page_html = uClient.read()
uClient.close()

page_soup = soup(page_html, "html.parser")
containers = page_soup.find_all("section",{"class":"ems-sdk-product-tile__details"})



for container in containers:
    try:
        title = container.span.get_text()
        print(title)

        salePercentContainer = container.find("span",{"class":"psw-body-2 discount-badge discount-badge--undefined"})
        salePercent = salePercentContainer.get_text()
        print(salePercent)
        if salePercent is None:
            salePercent = 'none'
     except Exception as e:
        pass

priceContainer = container.strike
print(priceContainer)
price = priceContainer  # .text
if price is None:
    Rprice = container.find_all("span", {"class": "price"})
    price = Rprice[0].text
    print(price)

print("title: "   title)
print("sale percent: "   str(salePercent))
print("price: "   str(price))
 

Вывод:-

 Just Cause 4
Rocket Arena
Vigor
Rocket League®
Fortnite
Days Gone
-50%
God of War
Genshin Impact
Mortal Kombat X
Rogue Company
Crash Bandicoot™ N. Sane Trilogy
eFootball PES 2021 LITE
Apex Legends™
Fallout 4
-70%
Stranded Deep
MONSTER HUNTER: WORLD™
RESIDENT EVIL 7 biohazard
-50%
The Last Guardian
Bloodborne™
Horizon Zero Dawn: Complete Edition
Persona 5
Battlefield™ 1
NHL® 21
-52%
Wreckfest
-30%
The Last Of Us™ Remastered 
Until Dawn
inFAMOUS Second Son
Detroit: Become Human
Red Dead Online
SHAREfactory™
Brawlhalla
Hyper Scape
Rec Room
Bless Unleashed
RACING BROS
F1 2020
-50%
NBA 2K21
-50%
Spellbreak
SMITE
Grand Theft Auto V
Injustice™ 2
-75%
UFC® 4
-50%
SPIDER-MAN: FAR FROM HOME VIRTUAL REALITY EXPERIENCE
Dead Island Definitive Edition
MX vs ATV All Out
Hello Neighbor
NARUTO TO BORUTO: SHINOBI STRIKER
Tomb Raider: Definitive Edition
None
$26.99
title: Tomb Raider: Definitive Edition
sale percent: -50%
price: $26.99
 

Ответ №2:

Данные находятся в формате json в исходном коде html. Вы также можете извлечь это и проанализировать.

Просто отфильтруйте фрейм данных, чтобы показать то, что вы хотите.

 import requests
import pandas as pd
import json
from bs4 import BeautifulSoup

rows = []
for page in range(1,11):
    url = 'https://store.playstation.com/en-ca/category/85448d87-aa7b-4318-9997-7d25f4d275a4/%s' %page
    response = requests.get(url)
    soup = BeautifulSoup(response.text, 'html.parser')
    
    jsonStr = soup.find_all('script',{'type':'application/json'})[2].text
    jsonData = json.loads(jsonStr)
    
    state = jsonData['props']['apolloState']
    
    
    print ('Page: %s' %page)
    for k, v in state.items():
        if 'Product:' in k and '.price' in k:
            skuId = k.split('.price')[0][1:]
            title = jsonData['props']['apolloState'][skuId]['name']
            v.update({'title':title})
            rows.append(v)
        
df = pd.DataFrame(rows)
 

Вывод:

 print (df)
    basePrice discountedPrice  ... __typename                         title
0       $0.00        Included  ...   SkuPrice                  Just Cause 4
1       $6.99           $6.99  ...   SkuPrice                  Rocket Arena
2        Free            Free  ...   SkuPrice                         Vigor
3        Free            Free  ...   SkuPrice                Rocket League®
4        Free            Free  ...   SkuPrice                      Fortnite
..        ...             ...  ...        ...                           ...
472    $39.99           $9.99  ...   SkuPrice  MX vs. ATV Supercross Encore
473    $29.99          $29.99  ...   SkuPrice      ASTRO BOT Rescue Mission
474    $33.49          $33.49  ...   SkuPrice                    Descenders
475    $54.99          $21.99  ...   SkuPrice                    Fallout 76
476    $24.99          $24.99  ...   SkuPrice         LEGO® Jurassic World™

[477 rows x 10 columns]
 

Показать со скидкой:

 discounted_df = df[~df['discountText'].isnull()]
 

Вывод:

 print(discounted_df.head(5).to_string())
   basePrice discountedPrice discountText  isFree  isExclusive               serviceBranding                  upsellServiceBranding    upsellText __typename                      title
5     $49.99          $24.99         -50%   False        False  {'type': 'json', 'json': []}   {'type': 'json', 'json': ['PS_NOW']}      Included   SkuPrice                  Days Gone
13    $39.99          $11.99         -70%   False        False  {'type': 'json', 'json': []}                                   None          None   SkuPrice                  Fallout 4
16    $26.99          $13.49         -50%   False        False  {'type': 'json', 'json': []}                                   None          None   SkuPrice  RESIDENT EVIL 7 biohazard
22    $79.99          $38.39         -52%   False        False  {'type': 'json', 'json': []}                                   None          None   SkuPrice                    NHL® 21
23    $39.99          $27.99         -30%   False        False  {'type': 'json', 'json': []}  {'type': 'json', 'json': ['PS_PLUS']}  Save 5% more   SkuPrice                  Wreckfest