Очистить веб-элементы

Я работал с запросами там, где мне было так просто. Очистить веб-страницу и добавить ее в 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, а если мы его не находим, в основном пропускаем его.

То, что меня беспокоило:

  1. Что произойдет, если одно из операторов if не сработает? Неудачи, я имею в виду и т.д. product_image.attrs['content'] — Это закончится исключением, когда он остановит скрипт, чего я не хочу делать.
  2. Я почти уверен, что буду использовать except Exception: # noqa это плохая практика, и я не знаю, как лучше с ней справиться.

Я был бы признателен за всевозможные советы и рекомендации, а также за то, как я могу улучшить свои знания с помощью Python!

2 ответа
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).

  • Привет! Извините за поздний ответ! Просто попал в это. Полностью согласен с вами по большей части, и есть вещи, о которых я бы никогда не подумал. Что касается части и т. Д., Если веб-страница имеет новый дизайн, что делать? Как вы писали, вы сделали if None in (product_name, product_price, product_image): Если я правильно понял, допустим, если мы выполняем 10 запросов подряд, а в 7-м цикле они меняют веб-элемент на что-то еще. Вы имеете в виду, что, используя этот код, чтобы увидеть, все ли эти 3 значения None, произошло ли новое веб-изменение?

    — Транспортир

  • 1

    Проще говоря, идея состоит в том, чтобы проверить, что каждый элемент возвращает значимое значение, по крайней мере, не None. Если какое-либо из ожидаемых полей не дает значения, значит, что-то не так. Это может быть редизайн сайта или ошибка в вашей программе. Помимо обработки исключений запросов вы также должны проверить код состояния. Должно быть 200. Если это 404, 401 или 500, то у вас другая проблема. И последнее, но не менее важное: я рекомендую подделать пользовательский агент в противном случае веб-сайту будет очевидно, что вы бот. Некоторые сайты могут даже отказать вам только по этой причине.

    — анонимный

  • Очень интересно. Yupp, что касается исключения, я, конечно, поработаю над этим. Я все еще не уверен в элементе проверки, который возвращает значимое значение. Хотим ли мы проверить, чтобы все они имели значение или хотя бы одно из них, например: product_name, product_price, product_image иметь ценность?

    — Транспортир

  • Вы не описали цель 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

    Кстати, большое спасибо за помощь! Есть очень хорошие вещи, о которых я бы никогда не подумал, и мне было действительно интересно посмотреть, как вам это удалось так хорошо!

    — Транспортир

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *