2

我将抓取的网址保存在 Mysql 数据库中。当scrapy 再次抓取网站时,如果它的 url 不在数据库中,调度程序或下载器应该只点击/抓取/下载页面。

#settings.py
DOWNLOADER_MIDDLEWARES = {
     'myproject.middlewares.RandomUserAgentMiddleware': 400,
     'myproject.middlewares.ProxyMiddleware': 410,
     'myproject.middlewares.DupFilterMiddleware': 390,
     'scrapy.contrib.downloadermiddleware.useragent.UserAgentMiddleware': None
    # Disable compression middleware, so the actual HTML pages are cached
}

#middlewares.py
class DupFilterMiddleware(object):
    def process_response(self, request, response, spider):
        conn = MySQLdb.connect(user='dbuser',passwd='dbpass',db='dbname',host='localhost', charset='utf8', use_unicode=True)
        cursor = conn.cursor()
        log.msg("Make mysql connection", level=log.INFO)

        cursor.execute("""SELECT id FROM scrapy WHERE url = %s""", (response.url))
        if cursor.fetchone() is None:
            return None
        else:
            raise IgnoreRequest("Duplicate --db-- item found: %s" % response.url)

#spider.py
class TestSpider(CrawlSpider):
    name = "test_spider"
    allowed_domains = ["test.com"]
    start_urls = ["http://test.com/company/JV-Driver-Jobs-dHJhZGVzODkydGVhbA%3D%3D"]

    rules = [
        Rule(SgmlLinkExtractor(allow=("http://example.com/job/(.*)",)),callback="parse_items"),
        Rule(SgmlLinkExtractor(allow=("http://example.com/company/",)), follow=True),
    ]

    def parse_items(self, response):
        l = XPathItemLoader(testItem(), response = response)
        l.default_output_processor = MapCompose(lambda v: v.strip(), replace_escape_chars)
        l.add_xpath('job_title', '//h1/text()')
        l.add_value('url',response.url)
        l.add_xpath('job_description', '//tr[2]/td[2]')
        l.add_value('job_code', '99')
        return l.load_item()

它有效,但我收到错误:从raise IgnoreRequest(). 是故意的吗?

2013-10-15 17:54:16-0600 [test_spider] ERROR: Error downloading <GET http://example.com/job/aaa>: Duplicate --db-- item found: http://example.com/job/aaa

我的方法的另一个问题是我必须查询我要抓取的每个网址。说,我有 10k 网址要抓取,这意味着我访问了 mysql 服务器 10k 次。我怎样才能在 1 mysql 查询中做到这一点?(例如,获取所有抓取的 url 并将它们存储在某处,然后针对它们检查请求 url)

更新:

遵循audiodude的建议,这是我最新的代码。但是,DupFilterMiddleware 停止工作。它运行 init 但不再调用 process_request。删除_init _将使process_request再次工作。我做错了什么 ?

class DupFilterMiddleware(object):
    def __init__(self):
        self.conn = MySQLdb.connect(user='myuser',passwd='mypw',db='mydb',host='localhost', charset='utf8', use_unicode=True)
        self.cursor = self.conn.cursor()

        self.url_set = set()
        self.cursor.execute('SELECT url FROM scrapy')
        for url in self.cursor.fetchall():
            self.url_set.add(url)

        print self.url_set

        log.msg("DupFilterMiddleware Initialize mysql connection", level=log.INFO)

    def process_request(self, request, spider):
        log.msg("Process Request URL:{%s}" % request.url, level=log.WARNING)
        if request.url in url_set:
            log.msg("IgnoreRequest Exception {%s}" % request.url, level=log.WARNING)
            raise IgnoreRequest()
        else:
            return None
4

1 回答 1

4

我能想到的几点:

首先,你应该process_request在你的DupFilterMiddleware. 这样,您甚至可以在请求被下载之前对其进行过滤。您当前的解决方案正在浪费大量时间和资源来下载最终被丢弃的页面。

其次,您不应该在 process_response/process_request 中连接到您的数据库。这意味着您正在为每个项目创建一个新的连接(并丢弃旧的)。这是非常低效的。尝试以下操作:

class DupFilterMiddleware(object):
  def __init__(self):
    self.conn = MySQLdb.connect(...
    self.cursor = conn.cursor()

然后cursor.execute(...在您的 process_response 方法中替换为self.cursor.execute(...

最后,我同意访问 MySQL 服务器 10k 次可能不是最优的。对于这么少的数据量,为什么不将它们全部加载到内存中的 set() 中。把它放在__init__你的下载器中间件的方法中:

self.url_set = set()
cursor.execute('SELECT url FROM scrapy')
for url in cursor.fetchall():
  self.url_set.add(url)

然后,无需执行查询和检查结果,只需执行以下操作:

if response.url in url_set:
  raise IgnoreRequest(...
于 2013-10-16T00:14:08.857 回答