Scrapy框架(下载项目图片以及实现爬虫数据持久化保存)scrapy shell

安装 Scrapy 框架

pip3 install Scrapy

Scrapy架构图(绿线是数据流向):

scrapy架构.png
  • Scrapy Engine(引擎): 负责Spider、ItemPipeline、Downloader、Scheduler中间的通讯,信号、数据传递等。

  • Scheduler(调度器): 它负责接受引擎发送过来的Request请求,并按照一定的方式进行整理排列,入队,当引擎需要时,交还给引擎。

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

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

  • Item Pipeline(管道):它负责处理Spider中获取到的Item,并进行进行后期处理(详细分析、过滤、存储等)的地方.

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

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

新建项目(scrapy startproject)

在开始爬取之前,必须创建一个新的Scrapy项目。进入自定义的项目目录中,运行下列命令:
scrapy startproject myspider

新建爬虫文件

** scrapy genspider jobbole jobbole.com**

关于yeild函数

参考资料说明:https://blog.csdn.net/u013205877/article/details/70332612 https://www.ibm.com/developerworks/cn/opensource/os-cn-python-yield/

简单地讲,yield 的作用就是把一个函数变成一个 generator(生成器),带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,带有yeild的函数遇到yeild的时候就返回一个迭代值,下次迭代时, 代码从 yield 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行, 直到再次遇到 yield。

通俗的讲就是:在一个函数中,程序执行到yield语句的时候,程序暂停,返回yield后面表达式的值,在下一次调用的时候,从yield语句暂停的地方继续执行,如此循环,直到函数执行完。

settings.py文件设置参考

  • 爬虫的文件路径
    SPIDER_MODULES = ['ziruproject.spiders']
    NEWSPIDER_MODULE = 'ziruproject.spiders'
  • 用户代理,一般设置这个参数用来伪装浏览器请求
    USER_AGENT = ''
  • 是否遵守ROBOT协议,为False时,表示不遵守, 为True时表示遵守(默认为True)
    ROBOTSTXT_OBEY = True
    Scrapy downloader(下载器) 处理的最大的并发请求数量。 默认: 16
    CONCURRENT_REQUESTS
  • 下载延迟的秒数,用来限制访问的频率
    默认为:0
    DOWNLOAD_DELAY

scrapy案例

以'http://chinaz.com/'为例
(下载项目图片以及实现爬虫数据持久化保存)

chinaz.py

# -*- coding: utf-8 -*-
import scrapy
from chinaz.items import ChinazprojectItem, ChinazprojectWebInfoItem

class ChinazSpider(scrapy.Spider):
    #爬虫名称
    name = 'china'
    #设置允许爬取的域
    allowed_domains = ['chinaz.com']
    #设置起始urls
    start_urls = ['http://top.chinaz.com/hangyemap.html']

    # 可以根据不同的爬虫文件做自定义的参数设置,会覆盖settings.py中的相关设置
    custom_settings = {
        'USER_AGENT' : 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
    }

    def parse(self, response):
        """
        在parse回调方法中
        step1:提取目标数据
        step2:获取新的url
        :param response: 请求的响应结果
        :return:
        """
        print(response.status)
        # response.xpath():使用xpath语法,得到的是SelectorList对象
        # response.css():使用css选择器,得到的是SelectorList对象
        # extract(): 将selector 序列化为unicode字符串
        # step1: 提取目标数据
        # 获取分类列表
        tags = response.xpath('//div[@class="Taright"]/a')
        # tags = response.css('.Taright a')
        for tag in tags:
            #实例化一个item,用来存储数据
            tag_item = ChinazprojectItem()
            #获取网站分类的名称
            # tagName = tag.xpath('./text()')[0].extract()
            tagName = tag.xpath('./text()').extract_first('')
            tag_item['tagName'] = tagName
            # 使用css取值(文本)
            # tagName = tag.css('::text').extract_first('')

            #获取网站分了的首页url地址
            # first_url = tag.xpath('./@href')[0].extract()
            first_url = tag.xpath('./@href').extract_first('')
            tag_item['firsturl'] = first_url
            #css语法取值(属性)
            # first_url = tag.css('::attr(href)').extract_first('')
            # print(tag_item)

            #将获取到的数据交给管道处理
            yield tag_item
            # http://top.chinaz.com/hangye/index_yule_yinyue.html
            '''
             url : 设置需要发起请求的url地址
             callback=None, : 设置请求成功后的回调方法
             method='GET', : 请求方式,默认为get请求
             headers=None, : 设置请求头,字典类型
             cookies=None, : 设置cookie信息,模拟登陆用户,字典类型
             meta=None, : 传参,字典类型
             encoding='utf-8', : 设置编码
             dont_filter=False, : 是否要去重,默认False表示去重
             errback=None, : 设置请求失败后的回调
            '''
            yield scrapy.Request(first_url,callback=self.parse_tags_page)

    def parse_tags_page(self,response):
        '''
        解析分类分页的网站信息
        :param response : 响应结果
        :return:
        '''
        print('分页请求', response.status, response.url)
        # 列表
        webInfos = response.xpath('//ul[@class="listCentent"]/li')
        for webinfo in webInfos:
            web = ChinazprojectWebInfoItem()
            # 封面图片
            web['coverImage'] = webinfo.xpath('//div[@class="leftImg"]/a/img/@src').extract_first('')
            # web['coverImages'] = ['']
            # 标题
            web['title'] = webinfo.xpath('//div[@class="CentTxt"]/h3/a/text()').extract_first('')
            # 域名
            web['domenis'] = webinfo.xpath('//div[@class="CentTxt"]/h3/span/text()').extract_first('')
            # 周排名
            web['weekRank'] = webinfo.xpath('//div[@class="CentTxt"]/div[@class="RtCPart clearfix"]/p[0]//a/text()').extract_first('')
            # 反链数
            web['ulink'] = webinfo.xpath('//div[@class="CentTxt"]/div[@class="RtCPart clearfix"]//p[3]//a/text()').extract_first('')
            # 网站简介
            web['info'] = webinfo.xpath('//div[@class="CentTxt"]/p/text()').extract_first('')
            # 得分
            web['score'] = webinfo.xpath('//div[@class="RtCRateCent"]/span/text()').extract_first('')
            # 排名
            web['rank'] = webinfo.xpath('//div[@class="RtCRateCent"]/strong/text()').extract_first('')
            # print(web)
            yield web
            #发起其他页面请求

            next_urls = response.xpath('//div[@class="ListPageErap"]/a/@href').extract()[1:]
            for next_url in next_urls:
                # 使用urljoin方法将不完整的url拼接完整
                next_url = response.urljoin(next_url)

                yield scrapy.Request(next_url,callback = self.parse_tags_page)

items.py

# -*- coding: utf-8 -*-
# Define here the models for your scraped items
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html
import scrapy

class ChinazprojectItem(scrapy.Item):
    '''
    存储网页分类信息
    '''
    # 分类名称
    tagName = scrapy.Field()
    # 分类首页url地址
    firsturl = scrapy.Field()

    def get_insert_spl_data(self, dataDict):
        '''
        step1 : 创建SQL语句
        step2 : 返回要存储的数据
        :param dataDict:
        :return:
        '''
        # 往数据库写
        sql = """
                INSERT INTO tags(%s)
                VALUES (%s)
                """ % (
            ','.join(dataDict.keys()),
            ','.join(['%s'] * len(dataDict))
        )
        # 需要往数据库中存储的数据
        data = list(dataDict.values())
        return sql, data

class ChinazprojectWebInfoItem(scrapy.Item):
    # 封面图片
    coverImage = scrapy.Field()
    # 标题
    title = scrapy.Field()
    # 域名
    domenis = scrapy.Field()
    # 周排名
    weekRank = scrapy.Field()
    # 反链接数
    ulink = scrapy.Field()
    # 网站简介
    info = scrapy.Field()
    # 得分
    score = scrapy.Field()
    # 排名
    rank = scrapy.Field()
    # 图片本地存储路径
    locakImagePath = scrapy.Field()

pipelines.py

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: https://doc.scrapy.org/en/latest/topics/item-pipeline.html

import mysql.connector as c
import pymongo
from scrapy.contrib.pipeline.images import ImagesPipeline
import scrapy
from chinaz.items import ChinazprojectWebInfoItem, ChinazprojectItem
from scrapy.utils.project import get_project_settings
import os

images_store = get_project_settings().get('IMAGES_STORE')

class ChinazProjectImagePipeline(ImagesPipeline):
    # 实现2个方法
    def get_media_requests(self, item, info):
        '''
        根据图片的url地址构造request请求
        :param item:
        :param info:
        :return:
        '''
        if isinstance(item,ChinazprojectWebInfoItem):
            # 获取图片地址
            image_url = 'http:' + item['coverImage']
            print('获取到图片地址', image_url)
            yield scrapy.Request(image_url)
            # 如果有多个图片地址item['coverImage']对应一个列表
            # image_urls = 'http:' + item['coverImage']
            # return [scrapy.Request(x) for x in image_urls]
            
    def item_completed(self, results, item, info):
       '''
       图片下载之后的回调方法
       :param results:[(True(表示图片是否下载成功),{'path':'图片下载之后的存储路径','url'.'图片url地址','checksum':'经过hash加密的一个字符串'})]
       :param item:
       :param info:
       :return:
       '''
       if isinstance(item,ChinazprojectWebInfoItem):
           paths = [result['path'] for status,result in results if status]
           print('图片下载成功', paths)
           if len(paths) > 0:
                print('图片获取成功')
                # 使用rname方法修改图片名称
                os.rename(images_store + '/' + paths[0],images_store + '/' + item['title'] + '.jpg')
                image_path = images_store + '/' + item['title'] + '.jpg'
                print('修改后的图片路径', image_path)
                item['localImagepath'] = image_path
           else:
                # 如果没有获取到图片吧这个item丢弃
                from scrapy.exceptions import DropItem
                raise DropItem('没有获取到图片,丢弃item')
           return item

# class ChinazprojectPipeline(object):
#
#     def __init__(self):
#         """
#         初始化方法
#         """
#         # self.file = open('chainz.json','a')
#         # 创建数据库连接
#         self.client = c.Connect(
#             host = '127.0.0.1', user = 'root', password = '123456',
#             database = 'chinaz', port = 3306, charset='utf8'
#         )
#         #创建游标
#         self.cursor = self.client.cursor()
#
#     def open_spider(self,spider):
#         """
#         爬虫启动的时候会调用一次
#         :param spider:
#         :return:
#         """
#         print('爬虫开启')
#
#     def process_item(self, item, spider):
#         """
#         这个方法是必须实现的,爬虫文件中所有的item
#         都会经过这个方法
#         :param item: 爬虫文件传递过来的item对象
#         :param spider: 爬虫文件实例化的对象
#         :return:
#         """
#         #存储到本地json文件
#         data_dict = dict(item)
#
#             # import json
#             # json_data = json.dumps(data_dict,ensure_ascii=False)
#             # self.file.write(json_data+'\n')
#             # 使用isinstance判断item要存储的表
#             # if isinstance(item, ChinazprojectWebInfoItem):
#             #     print('网站信息')
#             #     tablename = 'webinfo'
#             # elif isinstance(item, ChinazprojectItem):
#             #     print('网站分类信息')
#             #     tablename = 'tags'
#             #往数据库写
#             # sql = """
#             # INSERT INTO tags(%s)
#             # VALUES (%s)
#             # """ %(
#             #     ','.join(data_dict.keys()),
#             #     ','.join(['%s']*len(data_dict))
#             # )
#         if data_dict:
#
#             sql, data = item.get_insert_spl_data(data_dict)
#             try:
#                 # self.cursor.execute(sql,list(data_dict.values()))
#                 self.cursor.execute(sql, data)
#                 self.client.commit()
#             except Exception as err:
#                 self.client.rollback()
#                 print(err)
#
#             #如果有多个管道文件,一定要注意return item,
#             #否则下一个管道无法接收到item
#             print('经过了管道文件')
#             return item
#
#     def close_spider(self,spider):
#         """
#         爬虫结束的时候会调用一次
#         :param spider:
#         :return:
#         """
#         # self.file.close()
#         self.cursor.close()
#         self.client.close()
#         print('爬虫结束')

# 往MongoDB中插入数据
# class ChinazprojectPipeline(object):
#
#     def __init__(self,host,port,db):
#         #创建MongoDB的数据库链接
#         self.mongo_client = pymongo.MongoClient(
#             host='127.0.0.1',port=27017
#         )
#         # 获取要操作的数据库
#         self.db = self.mongo_client['db']
#
#     @classmethod
#     def from_crawler(cls,crawler):
#         '''
#         MONGODB_HOST = '127.0.0.1'
#         MONGODB_PORT= 27017
#         MONGODB_DB = "chinaz"
#         :param crawler:
#         :return:
#         '''
#         host = crawler.settings['MONGODB_HOST']
#         port = crawler.settings['MONGODB_PORT']
#         db = crawler.settings['MONGODB_DB']
#         return cls(host,port,db)
#
#     def process_item(self, item, spider):
#         '''
#         这个方法是必须实现的,爬虫文件中所有的item
#         都会经过这个方法
#         :param item: 爬虫文件传递过来的item对象
#         :param spider: 爬虫文件实例化的对象
#         :return:
#         '''
#         # 往哪个集合下插入数据
#         # 往集合下插入什么数据
#         col_name = item.get_mongodb_collectionName()
#         col = self.db[col_name]
#         dict_data = dict(item)
#         try:
#             col.insert(dict_data)
#             print('数据插入成功')
#         except Exception as err:
#             print('数据插入失败',err)
#         return item
#
#     def open_spider(self,spider):
#         print(spider.name,'爬虫开始运行')
#
#     def close_spider(self, spider):
#         # self.file.close()
#         self.cursor.close()
#         self.client.close()
#         print('爬虫结束')

#实现mysql数据库的异步插入(要插入的数据量非常大的情况下)
from twisted.enterprise import adbapi

class ChinazprojectPipeline(object):

    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_crawler(cls,cralwer):
        """
        MYSQL_HOST = '127.0.0.1'
        MYSQL_USER = 'root'
        MYSQL_PWD = 'ljh1314'
        MYSQL_DB = 'chainz'
        MYSQL_PORT = 3306
        MYSQL_CHARSET = 'utf8'
        :param cralwer:
        :return:
        """
        db_parmars = {
            'host':cralwer.settings['MYSQL_HOST'],
            'user':cralwer.settings['MYSQL_USER'],
            'passwd':cralwer.settings['MYSQL_PWD'],
            'db':cralwer.settings['MYSQL_DB'],
            'port':cralwer.settings['MYSQL_PORT'],
            'charset':cralwer.settings['MYSQL_CHARSET'],
        }

        dbpool = adbapi.ConnectionPool('pymysql',**db_parmars)

        return cls(dbpool)

    def process_item(self,item,spider):

        query = self.dbpool.runInteraction(
            self.insert_data_to_mysql,
            item
        )
        query.addErrback(
            self.insert_err,
            item
        )

        return item

    def insert_data_to_mysql(self,cursor,item):
        data_dict = dict(item)
        sql,data = item.get_insert_sql_data(data_dict)
        cursor.execute(sql,data)

    def insert_err(self,failure,item):
        print(failure,'插入失败',item)

scrapy shell

Scrapy终端是一个交互终端,我们可以在未启动spider的情况下尝试及调试代码,也可以用来测试XPath或CSS表达式,查看他们的工作方式,方便我们爬取的网页中提取的数据。

启动Scrapy Shell
scrapy shell "http://www.baidu.com/"
scrapy shell -s USER_AGENT=' '

Scrapy Shell根据下载的页面会自动创建一些方便使用的对象,例如 Response 对象,以及 Selector 对象 (对HTML及XML内容)。

当shell载入后,将得到一个包含response数据的本地 response 变量,输入 response.body将输出response的包体,输出 response.headers 可以看到response的包头。

输入 response.selector 时, 将获取到一个response 初始化的类 Selector 的对象,此时可以通过使用 response.selector.xpath()或response.selector.css() 来对 response 进行查询。

Scrapy也提供了一些快捷方式, 例如 response.xpath()或response.css()同样可以生效(如之前的案例)。

Selectors选择器 Scrapy Selectors 内置 XPath 和 CSS Selector 表达式机制 Selector有四个基本的方法,最常用的还是xpath:

xpath(): 传入xpath表达式,返回该表达式所对应的所有节点的selector list列表
extract(): 序列化该节点为字符串并返回list
css(): 传入CSS表达式,返回该表达式所对应的所有节点的selector list列表,语法同 BeautifulSoup4
re(): 根据传入的正则表达式对数据进行提取,返回字符串list列表

仅为个人学习小结,若有错处,欢迎指正~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,001评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,210评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,874评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,001评论 1 291
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,022评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,005评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,929评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,742评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,193评论 1 309
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,427评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,583评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,305评论 5 342
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,911评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,564评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,731评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,581评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,478评论 2 352

推荐阅读更多精彩内容

  • scrapy框架的流程图2018-11-01框架流程图 1、scrapy框架主要为个块      (1)Scrap...
    dream_seeker阅读 1,003评论 0 1
  • class RandomProxiesDownloadmidderware(object): def __in...
    会会_3a05阅读 401评论 0 0
  • Scrapy框架架构 Scrapy框架介绍: 写一个爬虫,需要做很多的事情。比如:发送网络请求、数据解析、数据存储...
    久壑阅读 878评论 0 0
  • 最近刘强东涉嫌强奸事件似乎有不了了之的趋势,对于这样的结果,感到有些遗憾。我并不清楚真正的事实如何,但假如刘强东真...
    NoSpringNoRain阅读 344评论 0 0
  • 这几天朋友圈被“剩女”二字霸屏,因为一则煽情的广告,成了80后90后甚至95后姑娘眼泪的导火点,why? 过年回家...
    小草根阅读 308评论 0 1