使用 Scrapy 串行爬取域
Crawling domains serially with Scrapy
我在数据库中有网站我想连续抓取,即:
- 将域添加到允许的域列表(应该为空)。
- 将此域添加到请求中(添加
http://
效果完美)。
- 抓取关于允许域的请求。
- 前进到下一个域,只将它添加到允许的域列表中。它将是那里唯一的一个,因此不会发生交叉。注意:criss-cross question 对我没有帮助,但也许我遗漏了什么...
- 抓取此请求。
- 依次完成我拥有的所有域。
到目前为止,我所取得的成就是抓取域,效果很好,完美地抓取了请求。
我遇到的唯一问题是 allowed_domains
似乎没有更新,它会抓取各种网站。
我设置了 DEPTH_LIMIT=1
所以它不会是无限爬行而且我还添加了 DF 爬行而不是 BF 爬行:
DEPTH_LIMIT= 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
这是我的蜘蛛的代码(只是开始;因为你并不真正关心我的项目处理...):
from __future__ import with_statement
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy.http.request import Request
from company.items import CompanyItem, UrlItem
from urlparse import urlparse
import MySQLdb
import json
class RecipeSpider(CrawlSpider):
name = "company"
allowed_domains = []
start_urls = []
rules = (Rule(LinkExtractor(allow=(), deny=(), tags=('a', 'link', 'li' 'area')), callback='extract_raw_recipe', follow=True),)
def start_requests(self):
# print "before sources"
sources = self.get_allowed_domains()
dummy_tuple = (long(len(sources) + 1), u'dummy', None, None, None, None, None, 0L, long(len(sources) + 1),)
final_sources = sources + (dummy_tuple,)
for source in final_sources:
self.allowed_domains = [str(source[1])]
url_string = str("http://" + source[1])
self.start_urls = [url_string]
self.update_running_source(source)
yield Request(url_string, self.extract_raw_recipe)
### start_requests()
def extract_raw_recipe(self, response):
pass
extract_raw_recipe
的代码没有请求或解析下一个 url,它可以正常工作,因此无需编辑。但是如果我需要在那里添加一些东西,请告诉我,因为也许这是缺少的 link。当前代码将字典添加到项目中,然后将此项目放入数据库中。
所以总而言之:我需要添加什么才能让它在每次抓取请求时过滤域?
如果需要提供更多代码,请告诉我。
我的建议是采用完全不同的方法。在需要抓取的域数据库中创建一个队列(基于您想要的任何标准),然后选择一个域进行抓取并使用 allowed_domains
列表中的相关域初始化蜘蛛。蜘蛛完成后,重新开始下一个域的另一个爬网。重复直到队列中的所有域都完成。
这将使您对整个过程有更多的控制(例如,重新排队失败的爬网、取消有问题的爬网并继续前进而不丢失进度、一次爬网多个域而不 "cross-talk" 等.) 如果您计划扩展它,它还允许您执行自定义设置(例如 USER_AGENT、DUPEFILTER、DOWNLOAD_DELAY)或基于每个域的自定义规则,显着扩展蜘蛛的可用性。
如果不行,您可以重置 allowed_domains
列表。这样做有一些问题,但首先是 Scrapy 的异地过滤的一些背景知识。
OffSiteMiddleware
负责根据allowed_domains
编制允许域列表。它为此使用了一个正则表达式,它只在蜘蛛启动时编译一次(使用 spider_opened
信号)。更新变量 allowed_domains
将不会对蜘蛛产生影响,除非您还强制 OffSideMiddleWare
重新编译其正则表达式。
以下方法(放置在您的蜘蛛中)应该可用于用新列表替换您的 allowed_domains
:
from scrapy.spidermiddlewares.offsite import OffsiteMiddleware
def change_allowed_domains(self, allowed_domains):
self.allowed_domains = allowed_domains
for middleware in self.crawler.engine.scraper.spidermw.middlewares:
if isinstance(middleware, OffsiteMiddleware):
middleware.spider_opened(self)
这将重置 OffsiteMiddleware
使用的 domains_seen
set()
,因此如果您将它用于其他用途,请记住这一点。
所以,花点时间吸收所有这些,一个问题开始浮出水面:当您在 start_requests()
中对每个域进行排队时,您当前更改 allowed_domains
的方法将行不通,因为Spider
class 只跟踪一个 allowed_domains
正则表达式(根本不与 request/response 对象关联)。如果在蜘蛛开始爬行之前将二十个请求排队到不同的域(每次更改 allowed_domains
列表),它将使用最近编译的正则表达式(即最后排队的 allowed_domains
域)。
为了克服这个问题,您需要抓取一个域的所有请求,编写您自己的 OffsiteMiddleware
带槽,并让它处理所有过滤。或者您可以创建一个不同的域,以便仅在队列中的所有请求都完成并且下载器中的所有插槽都为空(可能通过检查 self.crawler.engine.slot.inprogress
和 `self.crawler.engine.slot.scheduler)后才将下一个域添加到列表中。 =30=]
祝你好运!
我在数据库中有网站我想连续抓取,即:
- 将域添加到允许的域列表(应该为空)。
- 将此域添加到请求中(添加
http://
效果完美)。 - 抓取关于允许域的请求。
- 前进到下一个域,只将它添加到允许的域列表中。它将是那里唯一的一个,因此不会发生交叉。注意:criss-cross question 对我没有帮助,但也许我遗漏了什么...
- 抓取此请求。
- 依次完成我拥有的所有域。
到目前为止,我所取得的成就是抓取域,效果很好,完美地抓取了请求。
我遇到的唯一问题是 allowed_domains
似乎没有更新,它会抓取各种网站。
我设置了 DEPTH_LIMIT=1
所以它不会是无限爬行而且我还添加了 DF 爬行而不是 BF 爬行:
DEPTH_LIMIT= 1
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.FifoMemoryQueue'
这是我的蜘蛛的代码(只是开始;因为你并不真正关心我的项目处理...):
from __future__ import with_statement
import scrapy
from scrapy.spiders import CrawlSpider, Rule
from scrapy.linkextractors import LinkExtractor
from scrapy.http.request import Request
from company.items import CompanyItem, UrlItem
from urlparse import urlparse
import MySQLdb
import json
class RecipeSpider(CrawlSpider):
name = "company"
allowed_domains = []
start_urls = []
rules = (Rule(LinkExtractor(allow=(), deny=(), tags=('a', 'link', 'li' 'area')), callback='extract_raw_recipe', follow=True),)
def start_requests(self):
# print "before sources"
sources = self.get_allowed_domains()
dummy_tuple = (long(len(sources) + 1), u'dummy', None, None, None, None, None, 0L, long(len(sources) + 1),)
final_sources = sources + (dummy_tuple,)
for source in final_sources:
self.allowed_domains = [str(source[1])]
url_string = str("http://" + source[1])
self.start_urls = [url_string]
self.update_running_source(source)
yield Request(url_string, self.extract_raw_recipe)
### start_requests()
def extract_raw_recipe(self, response):
pass
extract_raw_recipe
的代码没有请求或解析下一个 url,它可以正常工作,因此无需编辑。但是如果我需要在那里添加一些东西,请告诉我,因为也许这是缺少的 link。当前代码将字典添加到项目中,然后将此项目放入数据库中。
所以总而言之:我需要添加什么才能让它在每次抓取请求时过滤域?
如果需要提供更多代码,请告诉我。
我的建议是采用完全不同的方法。在需要抓取的域数据库中创建一个队列(基于您想要的任何标准),然后选择一个域进行抓取并使用 allowed_domains
列表中的相关域初始化蜘蛛。蜘蛛完成后,重新开始下一个域的另一个爬网。重复直到队列中的所有域都完成。
这将使您对整个过程有更多的控制(例如,重新排队失败的爬网、取消有问题的爬网并继续前进而不丢失进度、一次爬网多个域而不 "cross-talk" 等.) 如果您计划扩展它,它还允许您执行自定义设置(例如 USER_AGENT、DUPEFILTER、DOWNLOAD_DELAY)或基于每个域的自定义规则,显着扩展蜘蛛的可用性。
如果不行,您可以重置 allowed_domains
列表。这样做有一些问题,但首先是 Scrapy 的异地过滤的一些背景知识。
OffSiteMiddleware
负责根据allowed_domains
编制允许域列表。它为此使用了一个正则表达式,它只在蜘蛛启动时编译一次(使用 spider_opened
信号)。更新变量 allowed_domains
将不会对蜘蛛产生影响,除非您还强制 OffSideMiddleWare
重新编译其正则表达式。
以下方法(放置在您的蜘蛛中)应该可用于用新列表替换您的 allowed_domains
:
from scrapy.spidermiddlewares.offsite import OffsiteMiddleware
def change_allowed_domains(self, allowed_domains):
self.allowed_domains = allowed_domains
for middleware in self.crawler.engine.scraper.spidermw.middlewares:
if isinstance(middleware, OffsiteMiddleware):
middleware.spider_opened(self)
这将重置 OffsiteMiddleware
使用的 domains_seen
set()
,因此如果您将它用于其他用途,请记住这一点。
所以,花点时间吸收所有这些,一个问题开始浮出水面:当您在 start_requests()
中对每个域进行排队时,您当前更改 allowed_domains
的方法将行不通,因为Spider
class 只跟踪一个 allowed_domains
正则表达式(根本不与 request/response 对象关联)。如果在蜘蛛开始爬行之前将二十个请求排队到不同的域(每次更改 allowed_domains
列表),它将使用最近编译的正则表达式(即最后排队的 allowed_domains
域)。
为了克服这个问题,您需要抓取一个域的所有请求,编写您自己的 OffsiteMiddleware
带槽,并让它处理所有过滤。或者您可以创建一个不同的域,以便仅在队列中的所有请求都完成并且下载器中的所有插槽都为空(可能通过检查 self.crawler.engine.slot.inprogress
和 `self.crawler.engine.slot.scheduler)后才将下一个域添加到列表中。 =30=]
祝你好运!