为什么 Scrapy 不遵循所有规则/运行 所有回调?
Why is Scrapy not following all rules / running all callbacks?
我有两个从父蜘蛛继承的蜘蛛 class 如下:
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.crawler import CrawlerProcess
class SpiderOpTest(CrawlSpider):
custom_settings = {
"USER_AGENT": "*",
"LOG_LEVEL": "WARNING",
"DOWNLOADER_MIDDLEWARES": {'scraper_scrapy.odds.middlewares.SeleniumMiddleware': 543},
}
httperror_allowed_codes = [301]
def parse_tournament(self, response):
print(f"Parsing tournament - {response.url}")
def parse_tournament_page(self, response):
print(f"Parsing tournament page - {response.url}")
class SpiderOpTest1(SpiderOpTest):
name = "test_1"
start_urls = ["https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/"]
rules = (Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),)
class SpiderOpTest2(SpiderOpTest):
name = "test_2"
start_urls = ["https://www.oddsportal.com/tennis/results/"]
rules = (
Rule(LinkExtractor(allow="/atp-buenos-aires/results/"), callback="parse_tournament", follow=True),
Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),
)
process = CrawlerProcess()
process.crawl(<spider_class>)
process.start()
第一个蜘蛛中 Rule
的 parse_tournament_page
回调工作正常。
然而,第二个蜘蛛只运行第一个 Rule
的 parse_tournament
回调,尽管第二个 Rule
与第一个蜘蛛相同并且正在运行同一页。
我显然遗漏了一些非常简单的东西,但我终究无法弄清楚它是什么......
由于页面的关键位是通过 Javascript 加载的,因此包含我正在使用的 Selenium 中间件可能对我有用:
from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver
class SeleniumMiddleware:
@classmethod
def from_crawler(cls, crawler):
middleware = cls()
crawler.signals.connect(middleware.spider_opened, signals.spider_opened)
crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
return middleware
def process_request(self, request, spider):
self.driver.get(request.url)
return HtmlResponse(
self.driver.current_url,
body=self.driver.page_source,
encoding='utf-8',
request=request,
)
def spider_opened(self, spider):
options = webdriver.FirefoxOptions()
options.add_argument("--headless")
self.driver = webdriver.Firefox(options=options)
def spider_closed(self, spider):
self.driver.close()
编辑:
所以我设法创建了第三个蜘蛛,它能够从内部执行 parse_tournament_page
回调 parse_tournament
:
class SpiderOpTest3(SpiderOpTest):
name = "test_3"
start_urls = ["https://www.oddsportal.com/tennis/results/"]
httperror_allowed_codes = [301]
rules = (
Rule(
LinkExtractor(allow="/atp-buenos-aires/results/"),
callback="parse_tournament",
follow=True,
),
)
def parse_tournament(self, response):
print(f"Parsing tournament - {response.url}")
xtr = LinkExtractor(allow="/page/")
links = xtr.extract_links(response)
for p in links:
yield response.follow(p.url, dont_filter=True, callback=self.parse_tournament_page)
def parse_tournament_page(self, response):
print(f"Parsing tournament PAGE - {response.url}")
这里的关键似乎是 dont_filter=True
- 如果将其保留为默认 False
则不会执行 parse_tournament_page
回调。这表明 Scrapy 以某种方式将第二页解释为重复,据我所知不是。除此之外,根据我读过的内容,如果我想解决这个问题,那么我需要将 unique=False
添加到 LinkExtractor
。但是,这样做不会导致 parse_tournament_page
回调执行 :(
更新:
所以我想我已经找到了问题的根源。据我所知,RFPDupeFilter
的 request_fingerprint
方法为 https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/ as https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/#/page/2/.
创建了相同的散列
通过阅读,我需要子class RFPDupeFilter
来重新配置request_fingerprint
的工作方式。任何关于为什么生成相同散列的建议 and/or 如何正确执行 subclass 的提示将不胜感激!
更新中提到的两个 URL 之间的区别在于片段#/page/2/。 Scrapy 默认忽略它们:另外,服务器在处理请求时通常会忽略 urls 中的片段,因此在计算指纹时也会默认忽略它们。如果要包含它们,请将 keep_fragments 参数设置为 True(例如,在使用无头浏览器处理请求时)。 (来自 scrapy/utils/request.py)
查看 DUPEFILTER_CLASS settings 了解更多信息。
来自scrapy.utils.request的request_fingerprint已经可以处理片段了。当 subclassing 通过 keep_fragments=True.
在 SpiderOpTest 的 custom_settings 中添加您的 class。
我有两个从父蜘蛛继承的蜘蛛 class 如下:
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
from scrapy.crawler import CrawlerProcess
class SpiderOpTest(CrawlSpider):
custom_settings = {
"USER_AGENT": "*",
"LOG_LEVEL": "WARNING",
"DOWNLOADER_MIDDLEWARES": {'scraper_scrapy.odds.middlewares.SeleniumMiddleware': 543},
}
httperror_allowed_codes = [301]
def parse_tournament(self, response):
print(f"Parsing tournament - {response.url}")
def parse_tournament_page(self, response):
print(f"Parsing tournament page - {response.url}")
class SpiderOpTest1(SpiderOpTest):
name = "test_1"
start_urls = ["https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/"]
rules = (Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),)
class SpiderOpTest2(SpiderOpTest):
name = "test_2"
start_urls = ["https://www.oddsportal.com/tennis/results/"]
rules = (
Rule(LinkExtractor(allow="/atp-buenos-aires/results/"), callback="parse_tournament", follow=True),
Rule(LinkExtractor(allow="/page/"), callback="parse_tournament_page"),
)
process = CrawlerProcess()
process.crawl(<spider_class>)
process.start()
第一个蜘蛛中 Rule
的 parse_tournament_page
回调工作正常。
然而,第二个蜘蛛只运行第一个 Rule
的 parse_tournament
回调,尽管第二个 Rule
与第一个蜘蛛相同并且正在运行同一页。
我显然遗漏了一些非常简单的东西,但我终究无法弄清楚它是什么......
由于页面的关键位是通过 Javascript 加载的,因此包含我正在使用的 Selenium 中间件可能对我有用:
from scrapy import signals
from scrapy.http import HtmlResponse
from selenium import webdriver
class SeleniumMiddleware:
@classmethod
def from_crawler(cls, crawler):
middleware = cls()
crawler.signals.connect(middleware.spider_opened, signals.spider_opened)
crawler.signals.connect(middleware.spider_closed, signals.spider_closed)
return middleware
def process_request(self, request, spider):
self.driver.get(request.url)
return HtmlResponse(
self.driver.current_url,
body=self.driver.page_source,
encoding='utf-8',
request=request,
)
def spider_opened(self, spider):
options = webdriver.FirefoxOptions()
options.add_argument("--headless")
self.driver = webdriver.Firefox(options=options)
def spider_closed(self, spider):
self.driver.close()
编辑:
所以我设法创建了第三个蜘蛛,它能够从内部执行 parse_tournament_page
回调 parse_tournament
:
class SpiderOpTest3(SpiderOpTest):
name = "test_3"
start_urls = ["https://www.oddsportal.com/tennis/results/"]
httperror_allowed_codes = [301]
rules = (
Rule(
LinkExtractor(allow="/atp-buenos-aires/results/"),
callback="parse_tournament",
follow=True,
),
)
def parse_tournament(self, response):
print(f"Parsing tournament - {response.url}")
xtr = LinkExtractor(allow="/page/")
links = xtr.extract_links(response)
for p in links:
yield response.follow(p.url, dont_filter=True, callback=self.parse_tournament_page)
def parse_tournament_page(self, response):
print(f"Parsing tournament PAGE - {response.url}")
这里的关键似乎是 dont_filter=True
- 如果将其保留为默认 False
则不会执行 parse_tournament_page
回调。这表明 Scrapy 以某种方式将第二页解释为重复,据我所知不是。除此之外,根据我读过的内容,如果我想解决这个问题,那么我需要将 unique=False
添加到 LinkExtractor
。但是,这样做不会导致 parse_tournament_page
回调执行 :(
更新:
所以我想我已经找到了问题的根源。据我所知,RFPDupeFilter
的 request_fingerprint
方法为 https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/ as https://www.oddsportal.com/tennis/argentina/atp-buenos-aires/results/#/page/2/.
通过阅读,我需要子class RFPDupeFilter
来重新配置request_fingerprint
的工作方式。任何关于为什么生成相同散列的建议 and/or 如何正确执行 subclass 的提示将不胜感激!
更新中提到的两个 URL 之间的区别在于片段#/page/2/。 Scrapy 默认忽略它们:另外,服务器在处理请求时通常会忽略 urls 中的片段,因此在计算指纹时也会默认忽略它们。如果要包含它们,请将 keep_fragments 参数设置为 True(例如,在使用无头浏览器处理请求时)。 (来自 scrapy/utils/request.py)
查看 DUPEFILTER_CLASS settings 了解更多信息。
来自scrapy.utils.request的request_fingerprint已经可以处理片段了。当 subclassing 通过 keep_fragments=True.
在 SpiderOpTest 的 custom_settings 中添加您的 class。