/ru-scrapy-python

Scrapy: примеры и полезная информация собранная участниками telegram чата @scrapy_python

made-with-markdown Chat GitHub stars GitHub contributors

В этом репозитории находится полезная информация, собранная участниками чата.

С чего начать?

Как ограничить количество реквестов?

  • CLOSESPIDER_PAGECOUNT = 10
  • И там же рядом полезные настройки вида: CLOSESPIDER_ITEMCOUNT, CLOSESPIDER_ERRORCOUNT, CLOSESPIDER_TIMEOUT, CLOSESPIDER_TIMEOUT_NO_ITEM

Как спарсить данные из JS?

  • смотреть откуда идут данные в Chrome -> devtools -> network -> XHR
  • JS to Python
  • Официальная документация рекомендует
  • ставится Splash(удобно в Docker) и плагин scrapy_splash (устарело)
  • Сейчас широко поддерживается и используется scrapy-playwright, под windows работает только из-под WSL2.

Лучшие практики

  • Использовать css селекторы чтобы избежать пробелов в названии при использовании @class в xpath, альтернатива "contains(@class, 'someclass')" выглядит сложнее.
  • Использовать xpath для поиска сложных значений, например в таблицах
  • Использовать корутины или asyncio для синхронных запросов в функции. Добавлен пример с asyncio.
  • Посмотреть мобильную версию

Популярные css селекторы

Пример xpath селекторов, которые обычно не вытащишь через css-селекторы

  • Получить предка от текущего элемента, например: response.xpath('./ancestor::a[contains(@class, "class_name")]/@href
  • Комментарии html, например: response.xpath('.//ul[@class="class_name"]/comment()[contains(.,"Артикул")]').get()

Полезные библиотеки

  • html_text - извлечь текст из сложного селектора, аналог .get_text(' ', strip=True) из BeautifulSoup, но быстрее и точнее.

Полезные браузерные расширения

  • Selector Gadget получить короткий css или xpath элемента(ов), см. видео на их сайте. Получается намного лучше встроенного в браузер copy as css/xpath.

Нельзя мешать yield и return?

После return жизни нет. Нужно возвращать список или что-то итерируемое.

Как вытащить узел по тексту внутри него используя css-селектор

Через CSS - никак. Использовать xpath contains. Документация по xpath.

Как поставить на windows

Простой способ - поставить в anaconda

Как достать items из последнего job-а в scrapinghub?

https://app.scrapinghub.com/api/items.json?project=PROJECT&spider=SPIDERNAME&apikey=KEY там где SPIDERNAME нужно вставить именно название, а не номер паука. дополнительно можно почитать тут

Как спарсить данные из нескольких форм с POST-запросами

Использовать цикл по форме c FormRequest.from_response, дополнительное поле со счетчиком формы formnumber=counter и с фильтром dont_filter=True.

Как обойти Cloudflare?

Страница отдает 503 ошибку. На этой странице javascript собирает код в форму с рандомным урлом и тремя hidden полями. После отправки этой формы отдается 302 редирект на нужную страницу.

Как передавать cookies

При надобности в передаче заранее подготовленных (например после авторизации на сайте) cookies, осуществить это можно через свой DownloaderMiddleware так:

  • В settings.py активируйте ваши DOWNLOADER_MIDDLEWARES
  • В settings.py убедиться, что значение по умолчанию COOKIES_ENABLED = True не переопределено на False, иначе scrapy не будет сохранять передаваемые ему страницой cookies.
  • В middlewares.py в методе обработки запросов process_request вашего DownloaderMiddleware прописать что-то такое:
def process_request(self, request, spider):
    request.cookies[cookiename] = value     # вставьте ваши значения
    return None
  • COOKIES_DEBUG = True в settings.py может помочь увидеть, что же происходит.

Где найти дефолтные настройки Scrapy?

default_settings.py в офф.репо

Как проанализировать запрос/форму?

Chrome -> devtools -> network -> клик на страницу -> copy as curl. Далее гуглим "curl to python", вставляем код и получаем распаршенный код в библиотеке requests
Если в Network в браузере поставить галочку напротив preserve log, то история запросов перестает очищаться при переходах между страницами.

Чем проанализировать пакеты сети или воспроизвести запрос/форму?

Fiddler или postman(он умеет сразу в питонкод конвертить). Мощнее и сложнее wireshark.

По умолчанию скрапи обрабатывает успешные ответы, для обработки остальных ответов используйте handle_httpstatus_list, например:

class MySpider(CrawlSpider):
    handle_httpstatus_list = [404]
  • также пригождается в редких случаях, если сайт отдает ошибку, но сам при этом показывает валидные данные, а scrapy ему "верит" - ответ не 200, и не парсит.

params в scrapy

В requests можно передать дополнительные параметры в GET методе:

import requests
params = [('q', 'scrapy')]
response = requests.get('https://github.com/search', params=params)

В scrapy можно сделать аналогично, через FormRequest:

FormRequest(
    url='https://github.com/search',
    method='GET',
    formdata=params,
    callback=self.parse_data,
)

Пример синхронного запроса с использованием async/await синтаксиса:

Для одного запроса:

from scrapy.utils.defer import maybe_deferred_to_future
class SingleRequestSpider(scrapy.Spider):
    name = "example"
    allowed_domains = ["https://books.toscrape.com"]
    start_urls = ["https://books.toscrape.com/catalogue/page-1.html"]

    async def parse(self, response, **kwargs):
        additional_request = scrapy.Request("https://books.toscrape.com/catalogue/the-black-maria_991/index.html")
        deferred = self.crawler.engine.download(additional_request)
        additional_response = await maybe_deferred_to_future(deferred)
        yield {
            "h1": response.css("h1::text").get(),
            "price": additional_response.css(".price_color::text").get(),
        }

Пример параллельных запросов с использованием asyncio:

import scrapy
from scrapy.utils.defer import maybe_deferred_to_future
import asyncio

class MultipleRequestsSpider(scrapy.Spider):
    name = "multiple"
    start_urls = ["https://books.toscrape.com/catalogue/page-1.html"]

    @classmethod
    def update_settings(cls, settings):
        settings["TWISTED_REACTOR"] = "twisted.internet.asyncioreactor.AsyncioSelectorReactor"

    async def parse(self, response, **kwargs):
        additional_requests = [
            scrapy.Request("https://books.toscrape.com/catalogue/the-black-maria_991/index.html"),
            scrapy.Request("https://books.toscrape.com/catalogue/the-requiem-red_995/index.html"),
        ]
        coroutines = []
        for r in additional_requests:
            deferred = self.crawler.engine.download(r)
            coroutines.append(maybe_deferred_to_future(deferred))
        responses = await asyncio.gather(
            *coroutines, return_exceptions=True
        )
        yield {
            'h1': response.css('h1::text').get(),
            'price': responses[0].css('.price_color::text').get(),
            'price2': responses[1].css('.price_color::text').get(),
        }

Деплой Scrapy

  • Хостинг Scrapinghub по дефолту стоит задержка, нужно отключать в настройках AUTOTHROTTLE_ENABLED чекбокс False
  • UI для Scrapy ScrapydWeb
  • Управление Scrapyd

Тесты

На сколько Scrapy быстрый?

Проверка N страниц.

  • requests в один поток - бесконечное время
  • scrapy из локальной машины - 30 минут
  • scrapinghub с включенным по дефолту тротлингом - больше 1 часа
  • scrapinghub без троттлинга 1 юнит - 23 минуты
  • scrapinghub без троттлинга 3 юнита - 15 минут

Можно ли использовать регулярные выражения в xpath?

Да, можно

Практика по регулярным выражениям. С чего начать?

Очистка текста от HTML тегов

Исходный текст

<p>Включает:</p><p>Клапан впускной / VALVE INLET АРТ: 3142H111		3	шт</p>

Удаление HTML тегов из текста без сохранения визуального переноса строк:

from w3lib.html import remove_tags
remove_tags(text)
Включает:Клапан впускной / VALVE INLET АРТ: 3142H111		3	шт                   

Удаление тегов из текста с сохранением визуального переноса строк с помощью библиотеки html2text

import html2text
html2text.html2text(text)
Клапан впускной / VALVE INLET АРТ: 3142H111 3 шт

Полезные ресурсы по Xpath

Справочники и туториалы с примерами:

Подборка cheatsheets и bestpractices по xpath