Я работал с запросами там, где мне было так просто. Очистить веб-страницу и добавить ее в dict и распечатать полезную нагрузку, если мы найдем новое значение или нет.
import json
import re
import requests
from selectolax.parser import HTMLParser
payload = {
"store": "Not found",
"name": "Not found",
"price": "Not found",
"image": "Not found",
"sizes": []
}
response = requests.get("https://shelta.se/sneakers/nike-air-zoom-type-whiteblack-cj2033-103")
if response.ok:
bs4 = HTMLParser(response.text)
product_name = bs4.css_first('h1[class="product-page-header"]')
product_price = bs4.css_first('span[class="price"]')
product_image = bs4.css_first('meta[property="og:image"]')
if product_name:
payload['name'] = product_name.text().strip()
if product_price:
payload['price'] = "{} Price".format(product_price.text().strip())
if product_image:
payload['image'] = product_image.attrs['content']
try:
attribute = json.loads(
'{}'.format(
re.search(
r'vars*JetshopDatas*=s*(.*?);',
response.text,
re.M | re.S
).group(1)
)
)
payload['sizes'] = [
f'{get_value["Variation"][0]}'
for get_value in attribute['ProductInfo']['Attributes']['Variations']
if get_value.get('IsBuyable')
]
except Exception: # noqa
pass
del bs4
print("New payload!", payload)
else:
print("No new payload!", payload)
Идея в основном заключается в том, что если мы находим значения, мы хотим заменить значения в dict, а если мы его не находим, в основном пропускаем его.
То, что меня беспокоило:
- Что произойдет, если одно из операторов if не сработает? Неудачи, я имею в виду и т.д.
product_image.attrs['content']
— Это закончится исключением, когда он остановит скрипт, чего я не хочу делать. - Я почти уверен, что буду использовать
except Exception: # noqa
это плохая практика, и я не знаю, как лучше с ней справиться.
Я был бы признателен за всевозможные советы и рекомендации, а также за то, как я могу улучшить свои знания с помощью Python!
2 ответа
Путаница в именах
Прежде всего, в вашем коде есть что-то непонятное: bs4 на самом деле не представляет собой экземпляр BeautifulSoup. Практически любой код Python, основанный на BeautifulSoup, имеет такую строку:
from bs4 import BeautifulSoup
Но в вашем коде bs4 представляет собой нечто другое: HTMLParser. Поэтому я бы использовал другое имя, чтобы прояснить, что это действительно не BS, и поэтому доступные методы совершенно разные. Меня это смутило:
bs4.css_first
потому что я не знаком с таким методом, присутствующим в BS4, и не без причины. В документация показывает, что он ведет себя как find
в BS и возвращает None, если не найден неподходящий элемент.
Вы подняли одну очевидную проблему: что произойдет, если одного (или нескольких) значений, которые вы пытаетесь получить, нет? Очевидно, вам следует проверить результаты, потому что рано или поздно дизайн веб-сайта изменится, и ваш код перестанет работать должным образом. Просто проверьте, что все ваши значения отличаются от None, используя any
или более просто:
if None in (product_name, product_price, product_image):
Если это условие выполнено, ваш код должен остановиться. В этом коде вы извлекаете только 3 элемента, но вы могли бы создать цикл (удобно, если ваш список будет длиннее). Все, что вам нужно, это базовый dict, который содержит ваши запросы xpath, например:
product_search = {
"name": 'h1[class="product-page-header"]',
"price": 'span[class="price"]',
"image": 'meta[property="og:image"]'
}
И если какой-либо из запросов xpath не завершается успешно, вы немедленно прерываете цикл.
Обработка исключений
То, что никогда не следует делать:
except Exception: # noqa
pass
Если возникает исключение, по крайней мере, зарегистрируйте его или распечатайте. Не выбрасывайте, не прячьте. Такая практика может привести только к ненадежным приложениям, которые трудно отлаживать. Если можете, избегайте исключения. Например, вы можете легко предвидеть, что ваши запросы xpath могут завершиться ошибкой, но результаты легко проверить.
Есть один тип исключения, с которым вы можете справиться, это request.exceptions. Потому что вполне возможно, что иногда веб-сайт может быть недоступен, или что ваше сетевое соединение не работает, или что DNS дает сбой.
В общем, хорошо иметь один универсальный обработчик исключений для всего приложения, а в определенных разделах кода вы можете добавить ограниченную обработку для определенных типов исключений. Таким образом, requests.get
должен быть в блоке попытки, например:
try:
response = requests.get("https://shelta.se/sneakers/nike-air-zoom-type-whiteblack-cj2033-103")
except requests.exceptions.RequestException as e:
# print error message and stop execution
Обратите внимание, что в этом примере я перехватываю базовое исключение, но вы можете обрабатывать разные типы исключений запросов отдельно (например: requests.exceptions.Timeout
).
- Вы не описали цель
payload
. Если это полезная нагрузка JSON, отправляемая какой-либо другой веб-службе,Not found
плохой выбор из-за отсутствующего значения иNone
было бы более подходящим - Вы никогда не используете
payload['store']
- Ваш селектор
h1[class="product-page-header"]
— что такое синтаксис xpath (?) — может быть простоh1.product-page-header
в синтаксисе селектора CSS - Я думаю, ваше регулярное выражение для
JetshopData
излишне снисходительно. Если формат нарушается, вы должны быть уведомлены об ошибке синтаксического анализа, а не молча пропускать измененный дизайн — поскольку формат внешнего словаря, скорее всего, будет не единственным, что нужно изменить. - Вы должны ограничить свое регулярное выражение только просмотром
<script>
значения, а не через весь HTML-документ '{}'.format
избыточен- Расскажи
requests
когда вы закончите с ответом через управление контекстом; наоборот, нет никакой пользы отdel bs4
если у вас есть подходящая область применения метода - Вероятно, вам следует смотреть на все варианты, а не только на первый
- Не одевайте-
except
. Если вы получили конкретное исключение, которое хотите проигнорировать, игнорируйте его в узком смысле. - Отделите код парсинга от кода формирования полезной нагрузки
Следующий предлагаемый код использует BeautifulSoup
потому что это то, с чем я более знаком, и я не хотел утруждать себя установкой selectolax
:
import json
import re
from dataclasses import dataclass
from pprint import pprint
from typing import Optional, List
import requests
from bs4 import BeautifulSoup
@dataclass
class Product:
name: Optional[str]
price: Optional[str]
image: Optional[str]
sizes: List[str]
@staticmethod
def get_sizes(doc: BeautifulSoup) -> List[str]:
pat = re.compile(
r'^<script>var JetshopData="
r"({.*})'
r';</script>$',
)
for script in doc.find_all('script'):
match = pat.match(str(script))
if match is not None:
break
else:
return []
data = json.loads(match[1])
return [
variation
for get_value in data['ProductInfo']['Attributes']['Variations']
if get_value.get('IsBuyable')
for variation in get_value['Variation']
]
@classmethod
def from_page(cls, url: str) -> Optional['Product']:
with requests.get(url) as response:
if not response.ok:
return None
doc = BeautifulSoup(response.text, 'html.parser')
name = doc.select_one('h1.product-page-header')
price = doc.select_one('span.price')
image = doc.select_one('meta[property="og:image"]')
return cls(
name=name and name.text.strip(),
price=price and price.text.strip(),
image=image and image['content'],
sizes=cls.get_sizes(doc),
)
@property
def payload(self) -> dict:
return {
"name": self.name or "Not found",
"price": self.price or "Not found",
"image": self.image or "Not found",
"sizes": self.sizes,
}
def main():
product = Product.from_page("https://shelta.se/sneakers/nike-air-zoom-type-whiteblack-cj2033-103")
if product is None:
print('No new payload')
else:
print('New payload:')
pprint(product.payload)
if __name__ == '__main__':
main()
Привет ! Мне, наверное, понадобится день, чтобы разобраться в этой функции, но я бы сказал, что это действительно здорово! Очень люблю это до сих пор! Однако меня мало беспокоит ваш
image=image and image['content']
— В основном, если мы не находим изображение[‘content’] тогда он потерпит неудачу и выдаст исключение Keyerror. Может нам нужно сделать что-то вроде"content" in image.attrs
?— Транспортир
Это правильно, но что делать, если он находит изображение, но не находит содержимое attrs? (В нашем случае это наверняка сработает, но если будут какие-либо изменения на веб-странице, где они не используют его в контенте)
— Транспортир
Если на веб-странице есть изменения, вам все равно придется переделать парсинг. Такова опасность соскабливания — работа со стабильным интерфейсом не гарантируется.
— Райндериен
Правильно, я не думаю, что есть реальный способ определить, была ли изменена веб-страница, например, я думаю. Конечно, я могу ошибаться, но завтра я напишу здесь лучший комментарий, так как здесь уже полночь 🙂 Но я уже вижу, что есть огромное улучшение!
— Транспортир
- 1
Кстати, большое спасибо за помощь! Есть очень хорошие вещи, о которых я бы никогда не подумал, и мне было действительно интересно посмотреть, как вам это удалось так хорошо!
— Транспортир
Привет! Извините за поздний ответ! Просто попал в это. Полностью согласен с вами по большей части, и есть вещи, о которых я бы никогда не подумал. Что касается части и т. Д., Если веб-страница имеет новый дизайн, что делать? Как вы писали, вы сделали
if None in (product_name, product_price, product_image):
Если я правильно понял, допустим, если мы выполняем 10 запросов подряд, а в 7-м цикле они меняют веб-элемент на что-то еще. Вы имеете в виду, что, используя этот код, чтобы увидеть, все ли эти 3 значения None, произошло ли новое веб-изменение?— Транспортир
Проще говоря, идея состоит в том, чтобы проверить, что каждый элемент возвращает значимое значение, по крайней мере, не None. Если какое-либо из ожидаемых полей не дает значения, значит, что-то не так. Это может быть редизайн сайта или ошибка в вашей программе. Помимо обработки исключений запросов вы также должны проверить код состояния. Должно быть 200. Если это 404, 401 или 500, то у вас другая проблема. И последнее, но не менее важное: я рекомендую подделать пользовательский агент в противном случае веб-сайту будет очевидно, что вы бот. Некоторые сайты могут даже отказать вам только по этой причине.
— анонимный
Очень интересно. Yupp, что касается исключения, я, конечно, поработаю над этим. Я все еще не уверен в элементе проверки, который возвращает значимое значение. Хотим ли мы проверить, чтобы все они имели значение или хотя бы одно из них, например:
product_name, product_price, product_image
иметь ценность?— Транспортир