Я написал простой пример использования 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()
@Cadence Да, это поведение по умолчанию, и я ничего не менял. Я ожидал, что ссылки в for link in self.link_extractor.extract_links(response)
не будут дубликатами.
Это может быть github.com/scrapy/scrapy/issues/3273
@wRAR Я попробовал PR № 6221, но это не сработало.
@whisperbye, тогда это либо другая проблема в Scrapy, либо проблема с вашим кодом, либо PR, в любом случае нам нужен минимальный воспроизводимый пример, поскольку код в посте не создает повторяющихся ссылок, и поэтому я предполагаю, что вы его используете на другом сайте.
@wRAR Спасибо за ваш ответ. Я изменил код в своем исходном вопросе.
@whisperbye Не думаю, что ты изменил что-то важное?
@wRAR В файле output.txt записаны повторяющиеся ссылки, которых, как я понимаю, там не должно быть.
Это просто означает, что отдельный вызов extract_links()
на разных страницах иногда приводит к извлечению повторяющихся ссылок. Ваш первоначальный вопрос выглядит так, будто речь идет об одном вызове. Вам следует перефразировать его и не ожидать, что дубликаты будут пропущены при нескольких вызовах.
@wRAR Извините, я новичок в использовании Scrapy, поэтому мой вопрос может быть не очень ясен. Как я могу удалить все ссылки с веб-сайта только один раз?
@whisperbye это поведение по умолчанию (из-за дублирующих фильтров). Если только вы не хотите на самом деле следовать им, в этом случае самый простой способ — выполнить дедупликацию выходного файла после завершения работы паука.
Чтобы предотвратить извлечение повторяющихся ссылок при сканировании веб-сайта с помощью 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 Извините за неудобства, я позаботился о том, чтобы быть более внимательным.
Это возможное решение, но я бы предпочел подойти к проблеме более фундаментально. Функция extract_links не соответствует описанному ожидаемому поведению.
(это совпадает с ним)
Согласно документации, «Дубликаты ссылки опускаются, если для уникального атрибута установлено значение True», что является поведением по умолчанию. Не могли бы вы попытаться уточнить свой вопрос немного больше и/или объяснить, что вы уже пробовали?