Как избежать извлечения повторяющихся ссылок в Python Scrapy?

Я написал простой пример использования Python Scrapy для перемещения по всем ссылкам на веб-сайте. Однако метод extract_links возвращает повторяющиеся адреса. Я попробовал несколько методов, но не добился никаких результатов.

Вот мой код:

import json
from typing import Any, Optional

import scrapy.linkextractors
import scrapy.spiders
from scrapy.crawler import CrawlerProcess
from scrapy.http import Response
from scrapy.utils.project import get_project_settings


class QuotesSpider(scrapy.Spider):
    name = "quotes"

    allowed_domains = [
        "quotes.toscrape.com"
    ]

    start_urls = [
        "https://quotes.toscrape.com/"
    ]

    def __init__(self, name: Optional[str] = None, **kwargs: Any):
        super().__init__(name, **kwargs)

        self.link_extractor = scrapy.linkextractors.LinkExtractor()

    def parse(self, response: Response, **kwargs: Any) -> Any:
        file_path = "output.txt"
        with open(file_path, "a") as output_file:
            for link in self.link_extractor.extract_links(response):
                result = {"nofollow": link.nofollow, "url": link.url, "text": link.text}
                json.dump(result, output_file)
                output_file.write('\n')
                yield response.follow(link, callback=self.parse)


if __name__ == "__main__":
    process = CrawlerProcess(get_project_settings())
    process.crawl(QuotesSpider)
    process.start()

Согласно документации, «Дубликаты ссылки опускаются, если для уникального атрибута установлено значение True», что является поведением по умолчанию. Не могли бы вы попытаться уточнить свой вопрос немного больше и/или объяснить, что вы уже пробовали?

Cadence 10.04.2024 03:38

@Cadence Да, это поведение по умолчанию, и я ничего не менял. Я ожидал, что ссылки в for link in self.link_extractor.extract_links(response) не будут дубликатами.

whisperbye 10.04.2024 04:35

Это может быть github.com/scrapy/scrapy/issues/3273

wRAR 10.04.2024 19:53

@wRAR Я попробовал PR № 6221, но это не сработало.

whisperbye 11.04.2024 03:37

@whisperbye, тогда это либо другая проблема в Scrapy, либо проблема с вашим кодом, либо PR, в любом случае нам нужен минимальный воспроизводимый пример, поскольку код в посте не создает повторяющихся ссылок, и поэтому я предполагаю, что вы его используете на другом сайте.

wRAR 11.04.2024 09:26

@wRAR Спасибо за ваш ответ. Я изменил код в своем исходном вопросе.

whisperbye 12.04.2024 04:18

@whisperbye Не думаю, что ты изменил что-то важное?

wRAR 12.04.2024 09:22

@wRAR В файле output.txt записаны повторяющиеся ссылки, которых, как я понимаю, там не должно быть.

whisperbye 12.04.2024 09:35

Это просто означает, что отдельный вызов extract_links() на разных страницах иногда приводит к извлечению повторяющихся ссылок. Ваш первоначальный вопрос выглядит так, будто речь идет об одном вызове. Вам следует перефразировать его и не ожидать, что дубликаты будут пропущены при нескольких вызовах.

wRAR 12.04.2024 09:48

@wRAR Извините, я новичок в использовании Scrapy, поэтому мой вопрос может быть не очень ясен. Как я могу удалить все ссылки с веб-сайта только один раз?

whisperbye 12.04.2024 10:25

@whisperbye это поведение по умолчанию (из-за дублирующих фильтров). Если только вы не хотите на самом деле следовать им, в этом случае самый простой способ — выполнить дедупликацию выходного файла после завершения работы паука.

wRAR 12.04.2024 12:39
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
11
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Чтобы предотвратить извлечение повторяющихся ссылок при сканировании веб-сайта с помощью Scrapy, вы можете сохранить набор посещенных URL-адресов и проверить, была ли посещена каждая ссылка ранее.

import json
import mimetypes
from typing import Any, Optional
import scrapy.linkextractors
import scrapy.spiders
from scrapy.http import Response
from scrapy.crawler import CrawlerProcess
from scrapy.utils.project import get_project_settings

# Define the allowed MIME types for downloading
ALLOWED_DOWNLOAD_TYPES = {
    "application/pdf",
    "application/msword",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
}

# Function to check if a MIME type is allowed for download
def is_download_type(typ: str) -> bool:
    return typ in ALLOWED_DOWNLOAD_TYPES

# Define the spider class
class QuotesSpider(scrapy.Spider):
    name = "quotes"

    allowed_domains = [
        "quotes.toscrape.com"
    ]

    start_urls = [
        "https://quotes.toscrape.com/"
    ]

    def __init__(self, name: Optional[str] = None, **kwargs: Any):
        super().__init__(name, **kwargs)

        if not mimetypes.inited:
            mimetypes.init()

        self.link_extractor = scrapy.linkextractors.LinkExtractor()

        # Track visited URLs to avoid duplicates
        self.visited_urls = set()
        
    def parse(self, response: Response, **kwargs: Any) -> Any:
        with open("output.txt", "w", encoding = "utf-8") as output_file:
            for link in self.link_extractor.extract_links(response):
                # Normalize URL for comparison
                normalized_url = response.urljoin(link.url)
                if normalized_url not in self.visited_urls:
                    self.visited_urls.add(normalized_url)
                    typ, _ = mimetypes.guess_type(link.url)
                    if typ is not None:
                        if typ == "text/html":
                            yield response.follow(link, callback=self.parse)
                        if is_download_type(typ):
                            result = {"nofollow": link.nofollow, "url": link.url, "text": link.text}
                            output_file.write(json.dumps(result) + '\n')

# Execute the spider
if __name__ == "__main__":
    process = CrawlerProcess(get_project_settings())
    process.crawl(QuotesSpider)
    process.start()

Эта установка делает прямо противоположное

whisperbye 10.04.2024 04:37

@whisperbye Извините за неудобства, я позаботился о том, чтобы быть более внимательным.

ApaxPhoenix 10.04.2024 17:56

Это возможное решение, но я бы предпочел подойти к проблеме более фундаментально. Функция extract_links не соответствует описанному ожидаемому поведению.

whisperbye 11.04.2024 03:43

(это совпадает с ним)

wRAR 12.04.2024 12:40

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