Scrapy Сканирует только первые 5 страниц сайта

Я работаю над решением следующей проблемы. Мой начальник хочет, чтобы я создал CrawlSpider в Scrapy, чтобы очистить детали статьи, такие как title, description, и разбить на страницы только первые 5 страниц.

Я создал CrawlSpider, но он разбивается на страницы со всех страниц. Как я могу ограничить CrawlSpider разбиением на страницы только первых последних 5 страниц?

Разметка страницы со списком статей сайта, которая открывается, когда мы нажимаем на следующую ссылку разбивки на страницы:

Разметка страницы со списком:

    <div class = "list">
      <div class = "snippet-content">
        <h2>
          <a href = "https://example.com/article-1">Article 1</a>
        </h2>
      </div>
      <div class = "snippet-content">
        <h2>
          <a href = "https://example.com/article-2">Article 2</a>
        </h2>
      </div>
      <div class = "snippet-content">
        <h2>
          <a href = "https://example.com/article-3">Article 3</a>
        </h2>
      </div>
      <div class = "snippet-content">
        <h2>
          <a href = "https://example.com/article-4">Article 4</a>
        </h2>
      </div>
    </div>
    <ul class = "pagination">
      <li class = "next">
        <a href = "https://www.example.com?page=2&keywords=&from=&topic=&year=&type = "> Next </a>
      </li>
    </ul>

Для этого я использую объект Rule с аргументом restrict_xpaths, чтобы получить все ссылки на статьи, а для последующего выполнения я выполняю метод класса parse_item, который будет получать статьи title и description из тегов meta.

Rule(LinkExtractor(restrict_xpaths='//div[contains(@class, "snippet-content")]/h2/a'), callback = "parse_item",
             follow=True)

Разметка страницы сведений:

<meta property = "og:title" content = "Article Title">
<meta property = "og:description" content = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.">

После этого я добавил еще один объект Rule для управления нумерацией страниц CrawlSpider будет использовать следующую ссылку, чтобы открыть другую страницу со списком и выполнять ту же процедуру снова и снова.

Rule(LinkExtractor(restrict_xpaths='//ul[@class = "pagination"]/li[@class = "next"]/a'))

Это мой код CrawlSpider:

from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
import w3lib.html


class ExampleSpider(CrawlSpider):
    name = "example"
    allowed_domains = ["example.com"]
    start_urls = ["https://www.example.com/"]
    custom_settings = {
        'FEED_URI': 'articles.json',
        'FEED_FORMAT': 'json'
    }
    total = 0

   
    rules = (
        # Get the list of all articles on the one page and follow these links
        Rule(LinkExtractor(restrict_xpaths='//div[contains(@class, "snippet-content")]/h2/a'), callback = "parse_item",
             follow=True),
        # After that get pagination next link get href and follow it, repeat the cycle
        Rule(LinkExtractor(restrict_xpaths='//ul[@class = "pagination"]/li[@class = "next"]/a'))
    )

    def parse_item(self, response):
        self.total = self.total + 1
        title = response.xpath('//meta[@property = "og:title"]/@content').get() or ""
        description = w3lib.html.remove_tags(response.xpath('//meta[@property = "og:description"]/@content').get()) or ""
       
        return {
            'id': self.total,
            'title': title,
            'description': description
        }

Есть ли способ ограничить поисковый робот сканированием только первых 5 страниц?

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
4
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Решение 1: используйте process_request.

from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


def limit_requests(request, response):
    # here we have the page number.
    # page_number = request.url[-1]
    # if int(page_number) >= 6:
    #     return None

    # here we use a counter
    if not hasattr(limit_requests, "page_number"):
        limit_requests.page_number = 0
    limit_requests.page_number += 1

    if limit_requests.page_number >= 5:
        return None

    return request


class ExampleSpider(CrawlSpider):
    name = 'example_spider'

    start_urls = ['https://scrapingclub.com/exercise/list_basic/']
    page = 0
    rules = (
        # Get the list of all articles on the one page and follow these links
        Rule(LinkExtractor(restrict_xpaths='//div[@class = "card-body"]/h4/a'), callback = "parse_item",
             follow=True),
        # After that get pagination next link get href and follow it, repeat the cycle
        Rule(LinkExtractor(restrict_xpaths='//li[@class = "page-item"][last()]/a'), process_request=limit_requests)
    )
    total = 0

    def parse_item(self, response):
        title = response.xpath('//h3//text()').get(default='')
        price = response.xpath('//div[@class = "card-body"]/h4//text()').get(default='')
        self.total = self.total + 1

        return {
            'id': self.total,
            'title': title,
            'price': price
        }

Решение 2: перезаписать метод _requests_to_follow (хотя должно быть медленнее).

from scrapy.http import HtmlResponse
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor


class ExampleSpider(CrawlSpider):
    name = 'example_spider'

    start_urls = ['https://scrapingclub.com/exercise/list_basic/']

    rules = (
        # Get the list of all articles on the one page and follow these links
        Rule(LinkExtractor(restrict_xpaths='//div[@class = "card-body"]/h4/a'), callback = "parse_item",
             follow=True),
        # After that get pagination next link get href and follow it, repeat the cycle
        Rule(LinkExtractor(restrict_xpaths='//li[@class = "page-item"][last()]/a'))
    )
    total = 0
    page = 0
    
    def _requests_to_follow(self, response):
        if not isinstance(response, HtmlResponse):
            return
        if self.page >= 5:  # stopping condition
            return
        seen = set()
        for rule_index, rule in enumerate(self._rules):
            links = [
                lnk
                for lnk in rule.link_extractor.extract_links(response)
                if lnk not in seen
            ]
            for link in rule.process_links(links):
                if rule_index == 1: # assuming there's only one "next button"
                    self.page += 1
                seen.add(link)
                request = self._build_request(rule_index, link)
                yield rule.process_request(request, response)

    def parse_item(self, response):
        title = response.xpath('//h3//text()').get(default='')
        price = response.xpath('//div[@class = "card-body"]/h4//text()').get(default='')
        self.total = self.total + 1

        return {
            'id': self.total,
            'title': title,
            'price': price
        }

Решения в значительной степени говорят сами за себя, если вы хотите, чтобы я что-то добавил, пожалуйста, спросите в комментариях.

спасибо за ответ, как мы можем передать счетчик страниц внутри функции limit_requests, потому что в URL-адресе нет страницы, а каждый список страниц имеет 10 ссылок?

Ven Nilson 11.04.2023 12:07

@VenNilson мы можем использовать счетчик внутри функции. Смотрите правку на solution 1.

SuperUser 11.04.2023 12:23

Другие вопросы по теме

Похожие вопросы

Как сохранить модель YOLOv8 после некоторого обучения на пользовательском наборе данных, чтобы продолжить обучение позже?
Можно ли интерполировать четверть видео оптическим потоком?
Сопоставьте значения в фрейме данных pandas и замените соответствующими значениями из главной таблицы
ValueError: невозможно переиндексировать ось с повторяющимися метками при использовании назначения
Как рисовать плавные линии на Tkinter
Создайте электронную таблицу из списка кортежей (строка, столбец, значение), используя двусвязный список (связанные списки связанных списков)
Я получаю эту ошибку, когда пытаюсь преобразовать список из Snowpark df в Pandas df: AttributeError: объект 'list' не имеет атрибута 'to_pandas'
Не удалось найти элемент с помощью Selenium и Python
Используя Python, Flask, SQLAlchemy, Marshmallow -- метод дампов возвращает пустой список объектов с моей схемой many=true
AttributeError: модуль «discord.ui» не имеет атрибута «ActionRow»