Python爬虫之scrapy框架基础理解

1 scrapy

1.1 简介

scrapy框架Python编写 ,是 一个快速、高层次的屏幕抓取和 web 抓取框架,用于抓取 web 站点并从页面中提取结构化的数据。
Scrapy 用途广泛,可以用于数据挖掘、监测和自动化测试,还有高性能的持久化存储,异步的数据下载,高性能的数据解析,分布式等等

1.1.1 scrapy原理

scrapy有五大核心组件:

  • 引擎:用来处理整个系统的数据流处理,触发事务(框架核心)
  • 调度器:用来接受引擎发过来的请求,压入队列中,并在引擎再次请求的时候返回,可以想象成一个URL的优先队列,由它来决定下一个喜欢的网址是什么,同时去除重复的网址
  • 下载器:用于下载网页内容,并将网页内容返回,scrapy下载是建立在twisted这个高效的异步模型上的
  • 爬虫:爬虫主要干活的,用于从特定网页中提取自己想要的信息,用户也可以从中爬取出链接,让Scrapy爬取下一个页面
  • 管道:负责处理页面中提取的实体,主要功能是持久化实体,验证实体的有效性,清除不需要信息,当页面被爬虫解析后,将解析发送到项目管道,并经过几个特定次序处理数据

1.2 环境安装

mac或者linux系统直接:pip install scrapy
windows系统,会比较麻烦:

  • 首先安装wheelpip install wheel

  • 下载twistedhttps://www.lfd.uci.edu/~gohlke/pythonlibs/#twisted,下载对应的whl文件。如下图,cp后面是python版本,amd64代表64位,划线的是我要下载的,点击下载

    在这里插入图片描述

  • 在下载后的twisted文件夹内执行pip命令来安装:pip install Twisted-20.3.0-cp38-cp38-win_amd64.whl
    如果安装报错:

ERROR: Exception:
Traceback (most recent call last):
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\base_command.py", line 173, in _main
status = self.run(options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\cli\req_command.py", line 203, in wrapper
return func(self, options, args)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\commands\install.py", line 315, in run
requirement_set = resolver.resolve(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\resolver.py", line 75, in resolve
collected = self.factory.collect_root_requirements(root_reqs)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 471, in collect_root_requirements
req = self._make_requirement_from_install_req(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 433, in _make_requirement_from_install_req
cand = self._make_candidate_from_link(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\factory.py", line 204, in _make_candidate_from_link
self._link_candidate_cache[link] = LinkCandidate(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 295, in init
super().init(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 156, in init
self.dist = self._prepare()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 227, in _prepare
dist = self._prepare_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\resolution\resolvelib\candidates.py", line 305, in _prepare_distribution
return self._factory.preparer.prepare_linked_requirement(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 508, in prepare_linked_requirement
return self._prepare_linked_requirement(req, parallel_builds)
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 570, in _prepare_linked_requirement
dist = _get_prepared_distribution(
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\operations\prepare.py", line 61, in _get_prepared_distribution
return abstract_dist.get_pkg_resources_distribution()
File "d:\software\codelanguages\python\python-file\python-3.9.4-embed-amd64\lib\site-packages\pip_internal\distributions\wheel.py", line 26, in get_pkg_resources_distribution
with ZipFile(self.req.local_file_path, allowZip64=True) as z:
File "zipfile.py", line 1257, in init
File "zipfile.py", line 1324, in _RealGetContents
zipfile.BadZipFile: File is not a zip file

可能是文件受损,重新下载执行上述命令即可成功

  • 安装pywin32pip install pywin32
  • 安装scrapypip install scrayp

最后测试是否安装成功,在DOS终端里录入scrapy指令,没有报错即表示安装成功

1.3 使用scrapy

1.3.1 使用步骤

  • 创建工程:scrapy startproject ProjectName
  • 创建爬虫文件
    先进入到工程目录中,然后执行命令scrapy genspider spiderFileName www.xxx.com,就会在spriders子目录中创建一个爬虫文件
    例如执行:scrapy genspider firstSpiderFile www.xxx.com会生成一个文件firstSpiderFile.py 且内部有后缀为Spider的类
    allowed_domains:允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
import scrapy

class FirstspiderfileSpider(scrapy.Spider):
    #爬虫文件名字,爬虫源文件一个唯一标识
    name = 'firstSpiderFile'
    #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy会自动发送请求
    start_urls = ['http://www.baidu.com/']

    #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
    def parse(self, response):
        print(response)

  • 执行工程,输入爬虫文件spiderFileNamescrapy crawl spiderFileName
  • 在爬虫文件中添加相关代码

注意:在爬虫文件使用xpath解析时,返回可能不是字符串,而是Selector对象,就可以用extract()方法解析`

1.4 持久化存储

1.4.1 基于终端指令

基于终端指令,只可以将parse方法的返回值存储到本地的文本文件中,但是只支持一下几种格式:'json', 'jsonlines', 'jl', 'csv', 'xml', 'marshal', 'pickle'
使用如下命令,就是在执行命令增加一个-o参数:

scrapy crawl spiderFileName -o 文件路径+文件名

import scrapy

class FirstspiderfileSpider(scrapy.Spider):
    #爬虫文件名字,爬虫源文件一个唯一标识
    name = 'firstSpiderFile'
    #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy会自动发送请求
    start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']

    #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
    def parse(self, response):
        div_list = response.xpath('//div[@class="shici_list_main"]')
        all_data=[]
        for div in div_list:
            #此处使用相对路径
            shici_name=div.xpath("h3/a/text()")[0].extract()
            # print(shici_name)
            shici_text_list=div.xpath("div//text()").extract()
            shici_text_list = "".join(shici_text_list)
            dict={
                'title':shici_name,
                "content":shici_text_list
            }
            all_data.append(dict)

        return all_data

注意:在爬虫文件的parse方法需要有返回值来保存文件

1.4.2 基于管道

1.4.2.1 基于管道步骤

基于管道大致步骤:(即把要写的代码写到items.py文件中)

  • item类中定义相关的属性(items.py)
  • 把要解析的数据封装存储到item类型对象(items.py)
  • item类的数据对象items.py提交给管道pipelines.py进行持久化存储操作
  • 在管道类的process_item中药将其接收到item对象中存储的数据进行持久化存储操作
  • 在配置文件settings.py中开启管道

1.4.2.2 基于管道操作

item类中定义相关的属性

import scrapy
class FirstItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    title=scrapy.Field()
    content=scrapy.Field()

把要解析的数据封装存储到item类型对象
注意:此处的爬虫文件是不需要return但是需要yield

import scrapy
from first.items import FirstItem

class FirstspiderfileSpider(scrapy.Spider):
    #爬虫文件名字,爬虫源文件一个唯一标识
    name = 'firstSpiderFile'
    #允许的域名,限定start_urls列表中哪些url可以发送请求,如果注释掉,会全部发送
    #allowed_domains = ['www.baidu.com']
    #起始的url列表:列表中的存放的url被scrapy会自动发送请求
    start_urls = ['https://www.test.com/chaxun/zuozhe/77.html']

    #用于数据解析:response参数表示请求成功后对应的响应对象,如果解析返回对象有多个,那么这个pass方法就要调用多次
    def parse(self, response):
        div_list = response.xpath('//div[@class="shici_list_main"]')
        all_data=[]
        for div in div_list:
            #此处使用相对路径
            shici_name=div.xpath("h3/a/text()")[0].extract()
            # print(shici_name)
            shici_text_list=div.xpath("div//text()").extract()
            shici_text_list = "".join(shici_text_list)
            
            item = FirstItem()
            item['title']=shici_name
            item['content']=shici_text_list
            yield item

pipelines.py开始持久化操作
注意:process_item中的return item,会把item传递给下一个即将被执行的管道类,不管后面有没有管道尽量都return

from itemadapter import ItemAdapter


class FirstPipeline(object):
    fp =None
    #重写父类一个方法:该方法只在开始爬虫的时候被调用一次
    def open_spider(self,spider):
        print("开始爬虫..............")
        self.fp=open('./test123.txt','w',encoding='utf-8')

    #专门用来处理item类型对象
    #可以接受爬虫文件体积过来的item对象
    #该方法每接受一个item对象就会被调用一次
    def process_item(self, item, spider):
        title=item['title']
        content=item['content']
        self.fp.write(title+":"+content+'\n')

        return item

    #继续重写父类方法
    def close_spider(self,spider):
        print('结束爬虫打印................')
        self.fp.close()

在配置文件settings.py中开启管道

ITEM_PIPELINES = {
    #300表示执行优先级,数值越小,优先级越高
   'first.pipelines.FirstPipeline': 300,
}

1.4.3 多渠道存储

如果要把爬取的数据分别存在本地和数据库中,那么就在pipelines.py中写多个管道操作类
pipelines.py文件中增加如下类

class MysqlPipeline(object):
    conn =None
    curcos=None
    #重写父类一个方法:该方法只在开始爬虫的时候被调用一次
    def open_spider(self,spider):
        print("mysql开始爬虫..............")
        self.conn=pymysql.Connect(host='127.0.0.1',port=3306,user='root',password='root',db='test',charset='utf-8')

    #专门用来处理item类型对象
    #可以接受爬虫文件体积过来的item对象
    #该方法每接受一个item对象就会被调用一次
    def process_item(self, item, spider):
        title=item['title']
        content=item['content']
        self.curcos=self.conn.cursor()
        self.curcos.execute('insert into test values("%s","%s")%(item["author"],item["content"]) ')
        self.curcos.commit()
        # 会把item传递给下一个即将被执行的管道类
        return item

    #继续重写父类方法
    def close_spider(self,spider):
        print('结束mysql爬虫...............')
        self.curcos.close()
        self.conn.close()

修改settings.py文件

ITEM_PIPELINES = {
    #300表示执行优先级,数值越小,优先级越高
   'first.pipelines.FirstPipeline': 300,
   'first.pipelines.MysqlPipeline': 301,
}

1.5 全站数据爬取

全站数据爬取就是将网站中某个板块下的全部页码对应的页码数据进行爬取
实现方式:

  • 将所有页面url添加到start_urls列表中
  • 自行手动请求,需要手动发送请求:yield scrapy.Request(url,callback)其中的callback是专门用于数据解析的

具体操作在爬虫文件中如下:

class XiaohuaSpider(scrapy.Spider):
    name='xiaohua'
    start_urls=['http://test.com/meinvxiaohua/']
    url='http://test.com/meinvxiaohua/?query=python&page=%d.html'
    def parse(self, response):
        li_list=response.xpath('//*[@id="content"]/div[2]')
        for li in li_list:
            img_name=li.xpath('./a[2]/b/text()').extract_first()

        if self.page_num<=3:#爬取前三页
            new_url=format(self.url%self.page_num)#替换新页面连接
            self.page_num+=1
            #手动发送请求:callback回调函数是专门用作数据解析
            yield scrapy.Request(url=new_url,callback=self.parse)#发送新的连接,同时还是使用parse方法进行解析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335