【问题标题】:do not crawl saved urls in database不抓取数据库中保存的 url
【发布时间】:2013-10-16 00:01:30
【问题描述】:

我将抓取的网址保存在 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()

它可以工作,但我收到 ERROR: Error download from 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

【问题讨论】:

    标签: scrapy


    【解决方案1】:

    我能想到的几点:

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

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

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

    然后将您的 process_response 方法中的 cursor.execute(... 替换为 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(...
    

    【讨论】:

    • 感谢您的回复。我根据您的建议更新我的问题。但是,当我有__init__ 时,process_request 不再运行。
    • 你确定它没有运行,还是它没有过滤任何东西?也许您应该在“返回无”之前添加一条日志消息。我有一种感觉 fetchall() 返回一个列表列表。即 [["url1.com"], ["url2.com"]], 所以你的 URL 永远不会在集合中。
    • 我 100% 确定。如果 process_request 运行,我应该打印出log.msg("Process Request URL:{%s}" % request.url, level=log.WARNING)。相反,我什么都没有。如果我删除 _init_,则会显示“处理请求 URL”
    • 你看到“DupFilterMiddleware Initialize mysql connection”消息了吗?
    • 是的,我在日志中看到“DupFilterMiddleware Initialize mysql connection”,但没有看到“Process Request URL”。
    猜你喜欢
    • 2019-08-22
    • 1970-01-01
    • 2022-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多