美文网首页
Scrapy入门学习

Scrapy入门学习

作者: LZY_c44b | 来源:发表于2018-09-06 15:49 被阅读0次

    简介

    采用异步框架。
    scrapy中文文档:
    https://scrapy-chs.readthedocs.io/zh_CN/0.24/intro/overview.html#
    优点:
    使用框架可以自己完成log ,并发,存储,监控。
    整体架构图:

    Scrapy架构图
    Scrapy Engine: 这是引擎,负责Spiders、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等

    Scheduler(调度器): 它负责接受引擎发送过来的requests请求,并按照一定的方式进行整理排列,入队、并等待Scrapy Engine(引擎)来请求时,交给引擎。

    Downloader(下载器):负责下载Scrapy Engine(引擎)发送的所有Requests请求,并将其获取到的Responses交还给Scrapy Engine(引擎),由引擎交给Spiders来处理,

    Spiders:它负责处理所有Responses,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器),

    Item Pipeline:它负责处理Spiders中获取到的Item,并进行处理,比如去重,持久化存储(存数据库,写入文件,总之就是保存数据用的)

    Downloader Middlewares(下载中间件):可以当作是一个可以自定义扩展下载功能的组件

    Spider Middlewares(Spider中间件):可以理解为是一个可以自定扩展和操作引擎和Spiders中间‘通信‘的功能组件(比如进入Spiders的Responses;和从Spiders出去的Requests)

    数据流向:

    1.引擎从调度器中取出一个链接(URL)用于接下来的抓取
    2.引擎把URL封装成一个请求(Request)传给下载器
    3.下载器把资源下载下来,并封装成应答包(Response)
    4.爬虫解析Response
    5.解析出实体(Item),则交给实体管道进行进一步的处理
    6.解析出的是链接(URL),则把URL交给调度器等待抓取

    新建工程

    scrapy startproject projectName

    这个命令会在当前目录下创建一个新目录projectName,此目录的结构如下:

    │  scrapy.cfg
    │
    └─projectName
        │  items.py
        │  pipelines.py
        │  settings.py
        │  __init__.py
        │
        └─spiders
                __init__.py
    

    scrapy.cfg: 项目配置文件
    projectName/: 项目python模块, 呆会代码将从这里导入
    projectName/items.py: 项目items文件
    projectName/pipelines.py: 项目管道文件
    projectName/settings.py: 项目配置文件
    projectName/spiders: 放置spider的目录

    定义Item

    类名必须含有Item。
    Items是将要装载抓取的数据的容器,在其中定义我们需要的数据。
    例如:

    class MyspiderItem(scrapy.Item):
           define the fields for your item here like:
           name = scrapy.Field()
    

    每一项都是一个list,存储json时需使用dict函数转换item,有时也需要根据需求对item进行额外的转换然后存入json文件中。

    json.dumps(dict(item), ensure_ascii=False)
    

    新建爬虫

    scrapy genspider [-t template] <name> <domain>
    

    在当前文件夹或当前项目的spiders文件夹中创建一个新的爬虫。该<name>参数设置为爬虫的name(不能与project重复),而<domain>用于生成allowed_domains和start_urls爬虫的属性。
    在spiders目录下生成文件:

    <name>.py
    

    运行爬虫

    命令行方式:

    scrapy crawl <name>
    

    IDE方式,需要在项目的根目录(与scrapy.cfg同级)创建一个入口脚本:

    from scrapy.cmdline import execute
    execute(['scrapy', 'crawl', '<name>'])
    

    这样能在IDE中运行调试。

    编写注意

    新建的爬虫文件中,会自动含有以下三个成员:

        name = '<name>'                                   #即新建爬虫的名字,在整个项目中有且只能有一个、名字不可重复
        allowed_domains = ['<name>.com']      
                                                          #这个不是必须的;但是在某写情况下需要用得到,
                                                          #比如使用爬取规则的时候就需要了;
                                                          #它的作用是只会跟进存在于allowed_domains中的URL。
                                                          #不存在的URL会被忽略。
        start_urls = [' ']                           #初始爬虫页面,根据需要进行调整
    

    一般我们需要对url进行拼接,然后调用Request进行并发处理:

        def start_requests(self):
            for i in range(1, 10):
                url = self.start_urls[0] + str(i) + self.bashurl
                yield Request(url, self.parse)                     #scrapy的优点,并发处理多个url
    

    生成的文件中会自包含一个类,这个类一般用作Request的回调函数,不要轻易改为他用:

        def parse(self, response):
    

    scrapy使用的是自带的Selector配合XPath解析获取需要的数据,但也可以使用BeautifulSoup解析(相比Selector慢)。
    XPath语法:
    http://www.w3school.com.cn/xpath/xpath_syntax.asp
    示例:

    for content_url in Selector(text=index_html).xpath('//div[@class="list post-list"]')
    

    导入item类时,需要加入路径,否则会一直提示找不到包,如下:

    fpath = os.path.abspath(os.path.join(os.path.dirname(__file__),".."))
    ffpath = os.path.abspath(os.path.join(fpath,".."))
    sys.path.append(ffpath)
    from mySpider.items import MyspiderItem
    

    item的传递

    yield Request(str(item["source_url"][0]), meta={'item': item}, callback=self.get_url_content)
    

    有时item在一个页面无法获取完整,需要在另一个页面继续获取,这时需要就要通过上面的方法传递。
    通过下面的方式取得传递的item:

    def get_url_content(self, response):
            item = response.meta['item']
            #return item    # ×××  有时会需要保存多中item,处理完一个item,这时不能直接return,否则程序就结束了
            #yield item      # √√√   正确方式
            #yield Request(url, meta={'item': item, 'rdt': rdt, 'comments':cmt,
            #    'rewards':rewards,'total': total, 'curpage': cur}, callback=self.processNextItem)
            # 传递多个参数
    

    区分不同的item

    if isinstance(item, MyspiderItem):
        ...
    else:
        ...
    

    配置

    在对应的setting文件中,可以进行一些配置。
    设置pipeline,需要在pipelines.py文件中编写对应函数进行处理,可以编写存储为json文件,也可以编写MySql存储(需进行登录等配置)。
    设置:

    ITEM_PIPELINES = {
       'mySpider.pipelines.MyspiderPipeline': 300,
    }
    

    mySpider(项目目录).pipelines.MyspiderPipeline(其中定义的类) 后面的 300是优先级程度(1-1000随意设置,数值越低,组件的优先级越高)。
    可根据需求定义多个类(存储为不同方式),设定不同的优先级。

    其中pipelines自己生成的类中有一个函数process_item,其参数item和spider是不可改变的,主要也是对item进行处理:

     def process_item(self, item, spider):
             ···
             return item     # 这个返回值是必须的
    

    注意存储文件时,可以事先打开一个文件,设定utf-8编码,因为Request得到的数据是unicode编码的。

    def __init__(self):
            self.file = codecs.open('smzdm.json', 'w+', encoding='utf-8')
    

    注意文件的默认保存目录是项目的根目录(与scrapy.cfg同级)

    去重

    有时会爬到重复的数据,需要进行去重后存放到文件或者数据库中。
    通常做法可以在pipelines中加入去重类,然后在settings中调高其优先级便可以达到此效果,但是有一个缺点是这种方法是在获取完所有数据后才去重。
    示例:

    url_count = 0
    
    class DuplicatesPipesline(object):
        def __init__(self):
            self.urls_seen = {}
            if os.path.exists("smzdm_dup.json"):
                self.urls_seen = json.loads(io.open("smzdm_dup.json", 'r', encoding='utf-8').read())
            #打开之前存储的去重数据文件
    
        def process_item(self, item, spider):
            global url_count
            url_count += 1
            if item["source_url"][0] in self.urls_seen:
                raise DropItem("Duplicate item found")     #丢弃掉重复数据
            else:
                self.urls_seen[item["source_url"][0]] = {}
    
                if url_count % 300 == 0:
                    io.open("smzdm_dup.json", "w", encoding="utf-8").write(
                        json.dumps(self.urls_seen, indent=4, sort_keys=True, ensure_ascii=False))
                return item
    
        def __del__(self):
            if url_count:
                io.open("smzdm_dup.json", "w", encoding="utf-8").write(
                    json.dumps(self.urls_seen, indent=4, sort_keys=True, ensure_ascii=False))
    
        # 最后将数据写入
    

    配置优先级:

    ITEM_PIPELINES = {
       'mySpider.pipelines.MyspiderPipeline': 300,
       'mySpider.pipelines.DuplicatesPipesline': 200,
    }
    

    相关文章

      网友评论

          本文标题:Scrapy入门学习

          本文链接:https://www.haomeiwen.com/subject/xwcgwftx.html