使用Scrapy爬取大量网站

4
我希望分析一些相互连接的网站(例如关于科幻小说的网站)的链接结构和文本内容。我有一个授权网站列表,约有300个,我想进行爬取。当我在数据库中拥有了抓取到的页面后,将使用其他工具对数据进行分析。
似乎Scrapy是执行这种任务的最佳工具之一,但我难以定义一个执行我所需功能的蜘蛛程序。我需要以下功能:
- 仅爬取特定域名(列表在外部文本文件中定义,可能会更改)。 - 将递归深度限制为给定值(例如3)。 - 对于每个页面,保存标题、HTML内容和链接到SQL Lite DB。 - 使用缓存避免下载相同页面的负载。缓存应具有过期日期(例如1周)。过期后,应重新爬取页面。 - 我想手动运行蜘蛛程序(目前不需要计划)。
为了实现这个目标,我已经开始这样定义一个蜘蛛程序:
# http://doc.scrapy.org/en/latest/intro/tutorial.html

from scrapy.spider import Spider
from scrapy import log
from scrapy.http.request import Request
from scrapy.contrib.spiders import CrawlSpider,Rule
from scrapy.contrib.linkextractors.sgml import SgmlLinkExtractor
from scrapy.selector import HtmlXPathSelector
from scrapy.selector import Selector
from ..items import PageItem

class PageSpider(CrawlSpider):
    name = "page"   

    rules = (Rule(SgmlLinkExtractor(allow=(),), callback='parse_item', follow=True),)   
    #restrict_xpaths=('//body',)), 

    def parse_item(self, response):
        log.msg( "PageSpider.parse" )
        log.msg( response.url )
        #sel = Selector(response)
        links = sel.xpath('//body//a/@href').extract()
        #log.msg("links")
        #log.msg(links)
        item = PageItem()
        item['url'] = response.url
        item['content'] = response.body
        item['links'] = "\n".join( links )
        return item

我该如何将允许的网站列表加载到Spider中的“allow”中呢? 为了存储这些项目,我使用了一个管道,似乎工作得不错(它还没有时间逻辑,但它可以将数据存储在本地数据库中):
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html
from scrapy import log
import sqlite3
import time
#from items import PageItem

class MyProjectPipeline(object):

    _db_conn = None;

    def process_item(self, item, spider):
        log.msg( "process item" )
        if not self.url_exists(item['url']):
            # insert element
            c = MyProjectPipeline._db_conn.cursor()
            items = [( item['url'], item['content'], item['links'], time.time() )]
            c.executemany('INSERT INTO pages_dump VALUES (?,?,?,?)', items)
            MyProjectPipeline._db_conn.commit()
        return item

    def open_spider(self, spider):
        # https://docs.python.org/2/library/sqlite3.html
        log.msg( "open sql lite DB" )
        MyProjectPipeline._db_conn = sqlite3.connect('consp_crawl_pages.db')
        c = MyProjectPipeline._db_conn.cursor()
        # create table
        c.execute('''create table if not exists pages_dump ( p_url PRIMARY KEY, p_content, p_links, p_ts )''')
        MyProjectPipeline._db_conn.commit()

    def close_spider(self, spider):
        log.msg( "closing sql lite DB" )
        MyProjectPipeline._db_conn.close()

    def url_exists(self, url):
        c = MyProjectPipeline._db_conn.cursor()
        c.execute("SELECT p_url FROM pages_dump WHERE p_url = ?", (url,))
        data=c.fetchone()
        if data is None:
            return False
        return True

我该如何阻止蜘蛛请求已经在数据库中存在的URL?
在Scrapy中,我所采用的方法是否明智?如果有更自然的方法,请告知。由于我的Python水平不是很高,所以编程建议也将受欢迎 :-)
感谢您的任何意见, Mulone

我认为在实际发送请求之前,使用下载器中间件来过滤重复的URL通常更好。请查看此问题以获取有关如何实现此操作的更多详细信息:https://dev59.com/XWAg5IYBdhLWcg3w9fDm - Talvalin
对于缓存,Scrapy 中有一个设置 https://doc.scrapy.org/en/latest/topics/downloader-middleware.html#std:setting-HTTPCACHE_ENABLED - Raheel
1个回答

2

我知道我的回答有些晚了,但是这是我回答你的问题的尝试:

1) 如果要爬取在txt文件中列出的域名,只需要在__init__方法中填写spider属性allowed_domains

class PageSpider(CrawlSpider):
    name = "page"
    def __init__(self, *args, **kwargs):
        self.allowed_domains = open('YOUR_FILE').read().splitlines()
        super(PageSpider, self).__init__(*args, **kwargs)

2) 要限制深度,只需设置DEPTH_LIMIT设置

3) 如果要将数据保存在数据库中,则使用管道是正确的方法。=)

4) Scrapy已经默认避免在同一次爬取中重复请求,但为了避免在以前的爬取中进行重复请求,您必须选择一种机制来在外部存储以前的爬取请求,并在spider middleware中过滤,如Talvalin在评论中链接的那个: https://dev59.com/XWAg5IYBdhLWcg3w9fDm#22968884


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接