美文网首页
2020-06-13

2020-06-13

作者: python小哥哥2020 | 来源:发表于2020-06-13 22:06 被阅读0次

大家好,我是天空之城,今天给大家带来,运用scrapy爬虫框架高效爬取数据和存储数据。
Scrapy的用法
0.创建Scrapy项目
1定 义item(数据)
2创建 和编写spiders文件
3修改settings.py文件
4运行Scrapy爬 虫


scrapy框架运行原理
scrapy框架文件结构

写代码:
明确目标
分析过程
代码实现(逐步)

以爬取豆瓣图书为演示https://book.douban.com/top250
豆瓣Top250图书一共有10页,每页有25本书籍。我们的目标是:先只爬取前三页书籍的信息,也就是爬取前75本书籍的信息(包含书名、出版信息和书籍评分)。
网址规律,
第2页的网址:
https:/ /book.douban.com/ top250?start=25
第3页的网址:
https:/ /book.douban.com/ top250?start=50

右击检查,你就会发现,其实每一页的25本书籍信息都分别藏在了一个<table width="100%">标签里。不过这个标签没有class属性,也没有id属性,不方便我们提取信息。

如果你想在自己本地的电脑使用Scrapy,需要提前安装好它。(安装方法:Windows:在终端输入命令:pip install scrapy;mac:在终端输入命令:pip3 install scrapy,按下enter键)

新建一个Python文件夹里的Pythoncode子文件夹。
然后,再输入一行能帮我们创建Scrapy项目的命令:scrapy startproject douban,douban就是Scrapy项目的名字。按下enter键,一个Scrapy项目就创建成功了。

Scrapy项目里每个文件都有特定的功能,比如settings.py 是scrapy里的各种设置。items.py是用来定义数据的,pipelines.py是用来处理数据的,它们对应的就是Scrapy的结构中的Item Pipeline(数据管道)。

如前所述,spiders是放置爬虫的目录。我们可以在spiders这个文件夹里创建爬虫文件。我们来把这个文件,命名为top250。后面的大部分代码都需要在这个top250.py文件里编写。

先在top250.py文件里导入我们需要的模块。
import  scrapy,bs4
在Scrapy中,每个爬虫的代码结构基本都如下所示:
class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['book.douban.com']
    start_urls = ['https://book.douban.com/top250?start=0']
    
    def parse(self, response):
        print(response.text)
第1行代码:定义一个爬虫类DoubanSpider。就像我刚刚讲过的那样,DoubanSpider类继承自scrapy.Spider类。
icon

第2行代码:name是定义爬虫的名字,这个名字是爬虫的唯一标识。name = 'douban'意思是定义爬虫的名字为douban。等会我们启动爬虫的时候,要用到这个名字。
icon

第3行代码:allowed_domains是定义允许爬虫爬取的网址域名(不需要加https://)。如果网址的域名不在这个列表里,就会被过滤掉。
icon

为什么会有这个设置呢?当你在爬取大量数据时,经常是从一个URL开始爬取,然后关联爬取更多的网页。比如,假设我们今天的爬虫目标不是爬书籍信息,而是要爬豆瓣图书top250的书评。我们会先爬取书单,再找到每本书的URL,再进入每本书的详情页面去抓取评论。
icon

allowed_domains就限制了,我们这种关联爬取的URL,一定在book.douban.com这个域名之下,不会跳转到某个奇怪的广告页面。
icon

第4行代码:start_urls是定义起始网址,就是爬虫从哪个网址开始抓取。在此,allowed_domains的设定对start_urls里的网址不会有影响。
icon

第6行代码:parse是Scrapy里默认处理response的一个方法,中文是解析。
icon

你或许会好奇,这里是不是少了一句类似requests.get()这样的代码?的确是,在这里,我们并不需要写这一句。scrapy框架会为我们代劳做这件事,写好你的请求,接下来你就可以直接写对响应如何做处理,我会在后面为你做示例。
icon

了解完爬虫代码的基础结构,我们继续来完善爬取豆瓣Top图书的代码。

我们可以利用豆瓣Top250图书的网址规律,用for循环构造出每个网址,再把网址添加进start_urls的列表里。这样代码会美观得多。

class DoubanSpider(scrapy.Spider):
    name = 'douban'
    allowed_domains = ['book.douban.com']
    start_urls = []
    for x in range(3):
        url = 'https://book.douban.com/top250?start=' + str(x * 25)
        start_urls.append(url)

接下来,只要再借助parse方法处理response,借助BeautifulSoup来取出我们想要的书籍信息的数据,代码即可完成。
icon

我们前面在分析项目过程的时候,已经知道书籍信息都藏在了哪些元素里,现在可以利用find_all和find方法提取出来。比如,书名是<tr class="item">元素下<a>元素的title属性的值;出版信息在<p class="pl">元素里;评分在<span class="rating_nums">元素里。
按照以前的习惯,
import scrapy
import bs4
from ..items import DoubanItem

class DoubanSpider(scrapy.Spider):
#定义一个爬虫类DoubanSpider。
    name = 'douban'
    #定义爬虫的名字为douban。
    allowed_domains = ['book.douban.com']
    #定义爬虫爬取网址的域名。
    start_urls = []
    #定义起始网址。
    for x in range(3):
        url = 'https://book.douban.com/top250?start=' + str(x * 25)
        start_urls.append(url)
        #把豆瓣Top250图书的前3页网址添加进start_urls。

    def parse(self, response):
    #parse是默认处理response的方法。
        bs = bs4.BeautifulSoup(response.text,'html.parser')
        #用BeautifulSoup解析response。
        datas = bs.find_all('tr',class_="item")
        #用find_all提取<tr class="item">元素,这个元素里含有书籍信息。
        for data in  datas:
        #遍历data。
            title = data.find_all('a')[1]['title']
            #提取出书名。
            publish = data.find('p',class_='pl').text
            #提取出出版信息。
            score = data.find('span',class_='rating_nums').text
            #提取出评分。
            print([title,publish,score])
            #打印上述信息。

-------------------------------------------------------------

spiders(如top250.py)只干spiders应该做的事。对数据的后续处理,另有人负责。

在scrapy中,我们会专门定义一个用于记录数据的类。

当我们每一次,要记录数据的时候,比如前面在每一个最小循环里,都要记录“书名”,“出版信息”,“评分”。我们会实例化一个对象,利用这个对象来记录数据。

每一次,当数据完成记录,它会离开spiders,来到Scrapy Engine(引擎),引擎将它送入Item Pipeline(数据管道)处理。

定义这个类的py文件,正是items.py。

我们已经知道,我们要爬取的数据是书名、出版信息和评分,我们来看看如何在items.py里定义这些数据。代码如下:
import scrapy
#导入scrapy
class DoubanItem(scrapy.Item):
#定义一个类DoubanItem,它继承自scrapy.Item
    title = scrapy.Field()
    #定义书名的数据属性
    publish = scrapy.Field()
    #定义出版信息的数据属性
    score = scrapy.Field()
    #定义评分的数据属性

第1行代码,我们导入了scrapy。目的是,我们等会所创建的类将直接继承scrapy中的scrapy.Item类。这样,有许多好用属性和方法,就能够直接使用。比如到后面,引擎能将item类的对象发给Item Pipeline(数据管道)处理。
icon

第3行代码:我们定义了一个DoubanItem类。它继承自scrapy.Item类。
icon

第5、7、9行代码:我们定义了书名、出版信息和评分三种数据。scrapy.Field()这行代码实现的是,让数据能以类似字典的形式记录。你可能不太明白这句话的含义,没关系。我带你来体验一下,你就能感受到是怎样一回事:
import scrapy
#导入scrapy
class DoubanItem(scrapy.Item):
#定义一个类DoubanItem,它继承自scrapy.Item
    title = scrapy.Field()
    #定义书名的数据属性
    publish = scrapy.Field()
    #定义出版信息的数据属性
    score = scrapy.Field()
    #定义评分的数据属性

book = DoubanItem()
# 实例化一个DoubanItem对象
book['title'] = '海边的卡夫卡'
book['publish'] = '[日] 村上春树 / 林少华 / 上海译文出版社 / 2003'
book['score'] = '8.1'
print(book)
print(type(book))

你会看到打印出来的结果的确和字典非常相像,但它却并不是dict,它的数据类型是我们定义的DoubanItem,属于“自定义的Python字典”。我们可以利用类似上述代码的样式,去重新写top250.py。如下所示:

import scrapy
import bs4
from ..items import DoubanItem
# 需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。

class DoubanSpider(scrapy.Spider):
#定义一个爬虫类DoubanSpider。
    name = 'douban'
    #定义爬虫的名字为douban。
    allowed_domains = ['book.douban.com']
    #定义爬虫爬取网址的域名。
    start_urls = []
    #定义起始网址。
    for x in range(3):
        url = 'https://book.douban.com/top250?start=' + str(x * 25)
        start_urls.append(url)
        #把豆瓣Top250图书的前3页网址添加进start_urls。

    def parse(self, response):
    #parse是默认处理response的方法。
        bs = bs4.BeautifulSoup(response.text,'html.parser')
        #用BeautifulSoup解析response。
        datas = bs.find_all('tr',class_="item")
        #用find_all提取<tr class="item">元素,这个元素里含有书籍信息。
        for data in  datas:
        #遍历data。
            item = DoubanItem()
            #实例化DoubanItem这个类。
            item['title'] = data.find_all('a')[1]['title']
            #提取出书名,并把这个数据放回DoubanItem类的title属性里。
            item['publish'] = data.find('p',class_='pl').text
            #提取出出版信息,并把这个数据放回DoubanItem类的publish里。
            item['score'] = data.find('span',class_='rating_nums').text
            #提取出评分,并把这个数据放回DoubanItem类的score属性里。
            print(item['title'])
            #打印书名。
            yield item
            #yield item是把获得的item传递给引擎。

在3行,我们需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。

当我们每一次,要记录数据的时候,比如前面在每一个最小循环里,都要记录“书名”,“出版信息”,“评分”。我们会实例化一个item对象,利用这个对象来记录数据。

每一次,当数据完成记录,它会离开spiders,来到Scrapy Engine(引擎),引擎将它送入Item Pipeline(数据管道)处理。这里,要用到yield语句。

yield语句你可能还不太了解,这里你可以简单理解为:它有点类似return,不过它和return不同的点在于,它不会结束函数,且能多次返回信息。

如果用可视化的方式来呈现程序运行的过程,就如同上图所示:爬虫(Spiders)会把豆瓣的10个网址封装成requests对象,引擎会从爬虫(Spiders)里提取出requests对象,再交给调度器(Scheduler),让调度器把这些requests对象排序处理。

然后引擎再把经过调度器处理的requests对象发给下载器(Downloader),下载器会立马按照引擎的命令爬取,并把response返回给引擎。

紧接着引擎就会把response发回给爬虫(Spiders),这时爬虫会启动默认的处理response的parse方法,解析和提取出书籍信息的数据,使用item做记录,返回给引擎。引擎将它送入Item Pipeline(数据管道)处理。

现在运行可能会失败,原因在于Scrapy里的默认设置没被修改。比如我们需要修改请求头。点击settings.py文件,你能在里面找到如下的默认设置代码:

# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'douban (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = True


请你把USER _AGENT的注释取消(删除#),然后替换掉user-agent的内容,就是修改了请求头。

又因为Scrapy是遵守robots协议的,如果是robots协议禁止爬取的内容,Scrapy也会默认不去爬取,所以我们还得修改Scrapy中的默认设置。

把ROBOTSTXT_OBEY=True改成ROBOTSTXT_OBEY=False,就是把遵守robots协议换成无需遵从robots协议,这样Scrapy就能不受限制地运行。

修改后的代码应该如下所示:

# Crawl responsibly by identifying yourself (and your website) on the user-agent
USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'

# Obey robots.txt rules
ROBOTSTXT_OBEY = False

想要运行Scrapy有两种方法,一种是在本地电脑的终端跳转到scrapy项目的文件夹(跳转方法:cd+文件夹的路径名),然后输入命令行:scrapy crawl douban(douban 就是我们爬虫的名字)。

另一种运行方式需要我们在最外层的大文件夹里新建一个main.py文件(与scrapy.cfg同级)。

我们只需要在这个main.py文件里,输入以下代码,点击运行,Scrapy的程序就会启动。
from scrapy import cmdline
#导入cmdline模块,可以实现控制终端命令行。
cmdline.execute(['scrapy','crawl','douban'])
#用execute()方法,输入运行scrapy的命令。

第1行代码:在Scrapy中有一个可以控制终端命令的模块cmdline。导入了这个模块,我们就能操控终端。

第3行代码:在cmdline模块中,有一个execute方法能执行终端的命令行,不过这个方法需要传入列表的参数。我们想输入运行Scrapy的代码scrapy crawl douban,就需要写成['scrapy','crawl','douban']这样。
至此,Scrapy的用法我们学完啦。

值得一提的是,在本关卡中为了教学方便理解,先写了爬虫,再定义数据。但是,在实际项目实战中,常常顺序却是相反的——先定义数据,再写爬虫。




另外一个案例
#导入模块:
import scrapy
import bs4
from ..items import JobuiItem

class JobuiSpider(scrapy.Spider):
    name = 'jobui'
    allowed_domains = ['www.jobui.com']
    start_urls = ['https://www.jobui.com/rank/company/']
    
#提取公司id标识和构造公司招聘信息的网址:
    def parse(self, response):
    #parse是默认处理response的方法
        bs = bs4.BeautifulSoup(response.text, 'html.parser')
        ul_list = bs.find_all('ul',class_="textList flsty cfix")
        for ul in ul_list:
            a_list = ul.find_all('a')
            for a in a_list:
                company_id = a['href']
                url = 'https://www.jobui.com{id}jobs'
                real_url = url.format(id=company_id)
                yield scrapy.Request(real_url, callback=self.parse_job)
#用yield语句把构造好的request对象传递给引擎。用scrapy.Request构造request对象。callback参数设置调用parsejob方法。

    def parse_job(self, response):
    #定义新的处理response的方法parse_job(方法的名字可以自己起)
        bs = bs4.BeautifulSoup(response.text, 'html.parser')
        #用BeautifulSoup解析response(公司招聘信息的网页源代码)
        company = bs.find(id="companyH1").text
        #用find方法提取出公司名称
        datas = bs.find_all('div',class_="c-job-list")
        #用find_all提取<div class_="c-job-list">标签,里面含有招聘信息的数据
        for data in datas:
        #遍历datas
            item = JobuiItem()
            #实例化JobuiItem这个类
            item['company'] = company
            #把公司名称放回JobuiItem类的company属性里
            item['position']=data.find('a').find('h3').text
            #提取出职位名称,并把这个数据放回JobuiItem类的position属性里
            item['address'] = data.find_all('span')[0]['title']
            #提取出工作地点,并把这个数据放回JobuiItem类的address属性里
            item['detail'] = data.find_all('span')[1]['title']
            #提取出招聘要求,并把这个数据放回JobuiItem类的detail属性里
            yield item
            #用yield语句把item传递给引擎

你应该不理解第22行代码:yield scrapy.Request(real_url, callback=self.parse_job)的意思。我跟你解释一下。
icon

scrapy.Request是构造requests对象的类。real_url是我们往requests对象里传入的每家公司招聘信息网址的参数。
icon

callback的中文意思是回调。self.parse_job是我们新定义的parse_job方法。往requests对象里传入callback=self.parse_job这个参数后,引擎就能知道response要前往的下一站,是parse_job()方法。
icon

yield语句就是用来把这个构造好的requests对象传递给引擎。
icon

第25行代码,是我们定义的新的parse_job方法。这个方法是用来解析和提取公司招聘信息的数据。

至此,我们整个项目还差存储数据这一步。在第6关,我们学过用csv模块把数据存储csv文件,用openpyxl模块把数据存储Excel文件。


其实,在Scrapy里,把数据存储成csv文件和Excel文件,也有分别对应的方法。我们先说csv文件。

存储成csv文件的方法比较简单,只需在settings.py文件里,添加如下的代码即可。
FEED_URI='./storage/data/%(name)s.csv'
FEED_FORMAT='CSV'
FEED_EXPORT_ENCODING='ansi'

FEED_URI是导出文件的路径。'./storage/data/%(name)s.csv',就是把存储的文件放到与main.py文件同级的storage文件夹的data子文件夹里。


FEED_FORMAT 是导出数据格式,写CSV就能得到CSV格式。

FEED_EXPORT_ENCODING 是导出文件编码,ansi是一种在windows上的编码格式,你也可以把它变成utf-8用在mac电脑上。


存储成Excel文件的方法要稍微复杂一些,我们需要先在settings.py里设置启用ITEM_PIPELINES,设置方法如下:

只要取消ITEM_PIPELINES的注释(删掉#)即可。
#取消`ITEM_PIPELINES`的注释后:

# Configure item pipelines
# See https://doc.scrapy.org/en/latest/topics/item-pipeline.html
ITEM_PIPELINES = {
     'jobui.pipelines.jobuiPipeline': 300,
}

接着,我们就可以去编辑pipelines.py文件。存储为Excel文件,我们依旧是用openpyxl模块来实现,代码如下,注意阅读注释:
import openpyxl

class JobuiPipeline(object):
#定义一个JobuiPipeline类,负责处理item
    def __init__(self):
    #初始化函数 当类实例化时这个方法会自启动
        self.wb =openpyxl.Workbook()
        #创建工作薄
        self.ws = self.wb.active
        #定位活动表
        self.ws.append(['公司', '职位', '地址', '招聘信息'])
        #用append函数往表格添加表头
        
    def process_item(self, item, spider):
    #process_item是默认的处理item的方法,就像parse是默认处理response的方法
        line = [item['company'], item['position'], item['address'], item['detail']]
        #把公司名称、职位名称、工作地点和招聘要求都写成列表的形式,赋值给line
        self.ws.append(line)
        #用append函数把公司名称、职位名称、工作地点和招聘要求的数据都添加进表格
        return item
        #将item丢回给引擎,如果后面还有这个item需要经过的itempipeline,引擎会自己调度

    def close_spider(self, spider):
    #close_spider是当爬虫结束运行时,这个方法就会执行
        self.wb.save('./jobui.xlsx')
        #保存文件
        self.wb.close()
        #关闭文件

在最后,我们还要再修改Scrapy中settings.py文件里的默认设置:添加请求头,以及把ROBOTSTXT_OBEY=True改成ROBOTSTXT_OBEY=False。
#需要修改的默认设置:

# Crawl responsibly by identifying yourself (and your website) on the user-agent
#USER_AGENT = 'jobui (+http://www.yourdomain.com)'

# Obey robots.txt rules
ROBOTSTXT_OBEY = True
还有一处默认设置我们需要修改,代码如下:

# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
#DOWNLOAD_DELAY = 0

我们需要取消DOWNLOAD_DELAY = 0这行的注释(删掉#)。DOWNLOAD_DELAY翻译成中文是下载延迟的意思,这行代码可以控制爬虫的速度。因为这个项目的爬取速度不宜过快,我们要把下载延迟的时间改成0.5秒。

改好后的代码如下:
# Configure a delay for requests for the same website (default: 0)
# See https://doc.scrapy.org/en/latest/topics/settings.html#download-delay
# See also autothrottle settings and docs
DOWNLOAD_DELAY = 0.5

大功告成

运行Scarpy的方法
在本地电脑的终端输入命令行: scrapy crawl +项目名
在Scrapy项目的根目录建立运行文件(与scrapy.cfg同级)


相关文章

  • 坑爹?爹挖的坑

    坑爹的儿子都是被爹坑的 白米先生 字数 474 · 阅读 1 2020-06-13 09:35 说起来像绕口令。 ...

  • 践行打卡第二个90天2/90

    2020-06-13 新一轮日更打卡2/90 打卡日期:20-6-13 星期六 21/35 天气:晴 起床:8点(...

  • 【D172】不掌握“提请求”的沟通要点,得到理想回应概率低——写

    2020-06-13,周六,晴 今天阅读《非暴力沟通》第六章。 Day139《不掌握“提请求”的沟通要点,得到理想...

  • 《 指数基金投资指南 》的笔记

    指数基金投资指南 银行螺丝钉 内容简介 2020-06-13 17:03 不管你是投资小白,还是进入股市厮杀多年也...

  • 2020-06-13|感恩日记

    2020-06-13|感恩日记 今天是星期六,天气晴朗,就是很热呀。 1.感恩身边的朋友,在即将分别的时候,给我寄...

  • 2020-06-13

    2020-06-13 文案达人必学的四类文案—— 怎么写有温度的文案 寒冬腊月,对广告人来讲,没有什么比一句...

  • 回家回家!!

    三日行 2020-06-13 现在手指因为中午网上那个切了那个辣椒搞到手后,啦,现在还是麻麻的感觉。唉,我也不知道...

  • 家有四宝!龙凤成长记之第34天

    2020-06-13 1. 儿子早上起来练大提琴,中午去上课,老公送的,奖赏他的进步吧,老师夸他震音好,他说老师是...

  • 周末,又到带娃出去放风的日子了

    2020-06-13周六 又到周末了。 疫情前,每个周末都会选择一天,带娃出去玩耍,另外一天就在家里收拾收拾、洗洗...

  • 2020-06-13

    一个人走,一直微笑面对,是因为无人在乎我是否哭泣,只好擦去眼泪继续上路。记忆中我总是在黑暗里爬行。

网友评论

      本文标题:2020-06-13

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