import hashlib import shutil import sys import tempfile from pathlib import Path from warnings import catch_warnings from testfixtures import LogCapture from scrapy.core.scheduler import Scheduler from scrapy.dupefilters import BaseDupeFilter, RFPDupeFilter from scrapy.exceptions import ScrapyDeprecationWarning from scrapy.http import Request from scrapy.utils.python import to_bytes from scrapy.utils.test import get_crawler from tests.spiders import SimpleSpider def _get_dupefilter(*, crawler=None, settings=None, open_=True): if crawler is None: crawler = get_crawler(settings_dict=settings) scheduler = Scheduler.from_crawler(crawler) dupefilter = scheduler.df if open_: dupefilter.open() return dupefilter class FromCrawlerRFPDupeFilter(RFPDupeFilter): @classmethod def from_crawler(cls, crawler): df = super().from_crawler(crawler) df.method = "from_crawler" return df class DirectDupeFilter: method = "n/a" class TestRFPDupeFilter: def test_df_from_crawler_scheduler(self): settings = { "DUPEFILTER_DEBUG": True, "DUPEFILTER_CLASS": FromCrawlerRFPDupeFilter, } crawler = get_crawler(settings_dict=settings) scheduler = Scheduler.from_crawler(crawler) assert scheduler.df.debug assert scheduler.df.method == "from_crawler" def test_df_direct_scheduler(self): settings = { "DUPEFILTER_CLASS": DirectDupeFilter, } crawler = get_crawler(settings_dict=settings) scheduler = Scheduler.from_crawler(crawler) assert scheduler.df.method == "n/a" def test_filter(self): dupefilter = _get_dupefilter() r1 = Request("http://scrapytest.org/1") r2 = Request("http://scrapytest.org/2") r3 = Request("http://scrapytest.org/2") assert not dupefilter.request_seen(r1) assert dupefilter.request_seen(r1) assert not dupefilter.request_seen(r2) assert dupefilter.request_seen(r3) dupefilter.close("finished") def test_dupefilter_path(self): r1 = Request("http://scrapytest.org/1") r2 = Request("http://scrapytest.org/2") path = tempfile.mkdtemp() try: df = _get_dupefilter(settings={"JOBDIR": path}, open_=False) try: df.open() assert not df.request_seen(r1) assert df.request_seen(r1) finally: df.close("finished") df2 = _get_dupefilter(settings={"JOBDIR": path}, open_=False) assert df != df2 try: df2.open() assert df2.request_seen(r1) assert not df2.request_seen(r2) assert df2.request_seen(r2) finally: df2.close("finished") finally: shutil.rmtree(path) def test_request_fingerprint(self): """Test if customization of request_fingerprint method will change output of request_seen. """ dupefilter = _get_dupefilter() r1 = Request("http://scrapytest.org/index.html") r2 = Request("http://scrapytest.org/INDEX.html") assert not dupefilter.request_seen(r1) assert not dupefilter.request_seen(r2) dupefilter.close("finished") class RequestFingerprinter: def fingerprint(self, request): fp = hashlib.sha1() fp.update(to_bytes(request.url.lower())) return fp.digest() settings = {"REQUEST_FINGERPRINTER_CLASS": RequestFingerprinter} case_insensitive_dupefilter = _get_dupefilter(settings=settings) assert not case_insensitive_dupefilter.request_seen(r1) assert case_insensitive_dupefilter.request_seen(r2) case_insensitive_dupefilter.close("finished") def test_seenreq_newlines(self): r"""Checks against adding duplicate \r to line endings on Windows platforms.""" r1 = Request("http://scrapytest.org/1") path = tempfile.mkdtemp() crawler = get_crawler(settings_dict={"JOBDIR": path}) try: scheduler = Scheduler.from_crawler(crawler) df = scheduler.df df.open() df.request_seen(r1) df.close("finished") with Path(path, "requests.seen").open("rb") as seen_file: line = next(seen_file).decode() assert not line.endswith("\r\r\n") if sys.platform == "win32": assert line.endswith("\r\n") else: assert line.endswith("\n") finally: shutil.rmtree(path) def test_log(self): with LogCapture() as log: settings = { "DUPEFILTER_DEBUG": False, "DUPEFILTER_CLASS": FromCrawlerRFPDupeFilter, } crawler = get_crawler(SimpleSpider, settings_dict=settings) spider = SimpleSpider.from_crawler(crawler) dupefilter = _get_dupefilter(crawler=crawler) r1 = Request("http://scrapytest.org/index.html") r2 = Request("http://scrapytest.org/index.html") dupefilter.log(r1, spider) dupefilter.log(r2, spider) assert crawler.stats.get_value("dupefilter/filtered") == 2 log.check_present( ( "scrapy.dupefilters", "DEBUG", "Filtered duplicate request: - no more" " duplicates will be shown (see DUPEFILTER_DEBUG to show all duplicates)", ) ) dupefilter.close("finished") def test_log_debug(self): with LogCapture() as log: settings = { "DUPEFILTER_DEBUG": True, "DUPEFILTER_CLASS": FromCrawlerRFPDupeFilter, } crawler = get_crawler(SimpleSpider, settings_dict=settings) spider = SimpleSpider.from_crawler(crawler) dupefilter = _get_dupefilter(crawler=crawler) r1 = Request("http://scrapytest.org/index.html") r2 = Request( "http://scrapytest.org/index.html", headers={"Referer": "http://scrapytest.org/INDEX.html"}, ) dupefilter.log(r1, spider) dupefilter.log(r2, spider) assert crawler.stats.get_value("dupefilter/filtered") == 2 log.check_present( ( "scrapy.dupefilters", "DEBUG", "Filtered duplicate request: (referer: None)", ) ) log.check_present( ( "scrapy.dupefilters", "DEBUG", "Filtered duplicate request: " " (referer: http://scrapytest.org/INDEX.html)", ) ) dupefilter.close("finished") def test_log_debug_default_dupefilter(self): with LogCapture() as log: settings = { "DUPEFILTER_DEBUG": True, } crawler = get_crawler(SimpleSpider, settings_dict=settings) spider = SimpleSpider.from_crawler(crawler) dupefilter = _get_dupefilter(crawler=crawler) r1 = Request("http://scrapytest.org/index.html") r2 = Request( "http://scrapytest.org/index.html", headers={"Referer": "http://scrapytest.org/INDEX.html"}, ) dupefilter.log(r1, spider) dupefilter.log(r2, spider) assert crawler.stats.get_value("dupefilter/filtered") == 2 log.check_present( ( "scrapy.dupefilters", "DEBUG", "Filtered duplicate request: (referer: None)", ) ) log.check_present( ( "scrapy.dupefilters", "DEBUG", "Filtered duplicate request: " " (referer: http://scrapytest.org/INDEX.html)", ) ) dupefilter.close("finished") class TestBaseDupeFilter: def test_log_deprecation(self): dupefilter = _get_dupefilter( settings={"DUPEFILTER_CLASS": BaseDupeFilter}, ) with catch_warnings(record=True) as warning_list: dupefilter.log(None, None) assert len(warning_list) == 1 assert ( str(warning_list[0].message) == "Calling BaseDupeFilter.log() is deprecated." ) assert warning_list[0].category == ScrapyDeprecationWarning