Python:Scrapy(二) 实例分析与总结、写一个爬虫的一般步骤
学习自:Scrapy爬虫框架教程(二)-- 爬取豆瓣电影TOP250 - 知乎
Python Scrapy 爬虫框架实例(一) - Blue·Sky - 博客园
1、声明Item
爬虫爬取的目标是从非结构性的数据源提取结构性的数据,例如网页。Spider可以以Dict类型来返回提取的数据。然而,虽然Dict很方便,但是缺少结构性,容易打错字段的名字或者返回不一致的数据,特别是用在具有多个Spider的大项目中。
为了定义常用的输出数据,Scrapy提供了Item类。Item对象是种简单的容器,保存了爬取到的数据。其提供了类似Dict的API以及用于声明可用字段的简单语法。许多Scrapy组件使用了Item提供的额外信息——根据Item声明的字段来导出数据、序列化可以通过Item定义的元数据、追踪Item实例来帮助寻找内存泄漏等等。
Item使用简单的class定义语法以及Field对象来声明。我们打开之前创建的项目下的items.py文件,写入以下代码声明Item
import scrapy class DoubanMovieItem(scrapy.item): # 排名 ranking = scrapy.Field() # 名称 name = scrapy.Field() # 评分 score = scrapy.Field() # 评论人数 num = scrapy.Field()
2、爬虫程序
在spiders目录下创建爬虫文件douban_spider.py,并写入初步的代码:
from scrapy.spiders import Spider from S.items import DoubanMovieItem class DoubanMovieTop250Spider(Spider): name = 'douban_movie_top250' start_urls = ['https://movie.douban.com/top250'] def parse(self, response): item=DoubanMovieItem()
这是一个基本的Spider的Model,首先我们要导入scrapy.spiders中的Spider类,以及我们刚刚定义好的scrapyspider.items中的DoubanMovieItem类。接着创建我们自己的Spider类DoubanMovieTop250Spider,该类继承自Spider类。scrapy.spiders中有许多不同的爬虫类可以供我们继承,一般情况下使用Spider类就可以满足要求。
3、提取网页信息
我们使用XPath语法来提取我们所需的信息。
首先我们在chrome浏览器中进入豆瓣电影TOP250页面并打开开发者工具。点击工具栏左上角的类鼠标图标或者CTRL + SHIFT + C在页面中点击我们想要的元素就可以看到它的HTML源码的位置。一般抓取的时候会以先抓大再抓小的原则来抓取。通过观察我们看到该页面的所有影片的信息都位于一个class属性为grid_view的ol标签的il标签内。
<ol class="grid_view"> <li> <div class="item"> <div class="pic"> <em class="">1</em> <a href="https://movie.douban.com/subject/1292052/"> <img width="100" alt="肖申克的救赎" src="https://img2.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp" class=""> </a> </div> <div class="info"> <div class="hd"> <a href="https://movie.douban.com/subject/1292052/" class=""> <span class="title">肖申克的救赎</span> <span class="title"> / The Shawshank Redemption</span> <span class="other"> / 月黑高飞(港) / 刺激1995(台)</span> </a> <span class="playable">[可播放]</span> </div> <div class="bd"> <p class=""> 导演: 弗兰克·德拉邦特 Frank Darabont 主演: 蒂姆·罗宾斯 Tim Robbins /...<br> 1994 / 美国 / 犯罪 剧情 </p> <div class="star"> <span class="rating5-t"></span> <span class="rating_num" property="v:average">9.7</span> <span property="v:best" content="10.0"></span> <span>2317522人评价</span> </div> <p class="quote"> <span class="inq">希望让人自由。</span> </p> </div> </div> </div> </li> …… </ol>
因此我们根据以上原则对所需信息进行抓取
from scrapy.spiders import Spider from S.items import DoubanMovieItem class DoubanMovieTop250Spider(Spider): name = 'douban_movie_top250' start_urls = ['https://movie.douban.com/top250'] def parse(self, response): item = DoubanMovieItem() movies=response.xpath('//ol[@class="grid_view"]/li') for movie in movies: item['ranking'] = movie.xpath( './/div[@class="pic"]/em/text()').extract()[0] item['name'] = movie.xpath( './/div[@class="hd"]/a/span[1]/text()').extract()[0] item['score'] = movie.xpath( './/div[@class="star"]/span[@class="rating_num"]/text()' ).extract()[0] item['num'] = movie.xpath( './/div[@class="star"]/span/text()').re(r'(\d+)人评价')[0] yield item
对于Scrapy提取页面信息的内容详情可以参照官方文档
4、运行爬虫
在项目文件夹内打开cmd运行一下命令
scrapy crawl douban_movie_top250 -o douban.csv
注意此处的douban_movie_top250即我们刚刚写的爬虫的name,而-o douban.csv是scrapy提供的将Item输出位CSV格式的快捷方式。
运行时可能会出现403错误,这是因为豆瓣对于爬虫设置了门槛,我们需要在发送请求时附加请求头headers及'User-Agent'项,修改后的代码如下:
from scrapy import Request from scrapy import Spider from S.items import DoubanMovieItem class DoubanMovieTop250Spider(Spider): name='douban_movie_top250' headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', } def start_requests(self): url='https://movie.douban.com/top250' yield Request(url,headers=self.headers) def parse(self,response): item=DoubanMovieItem() movies=response.xpath('//ol[@class="grid_view"]/li') for movie in movies: item['ranking'] = movie.xpath( './/div[@class="pic"]/em/text()').extract()[0] item['name'] = movie.xpath( './/div[@class="hd"]/a/span[1]/text()').extract()[0] item['score'] = movie.xpath( './/div[@class="star"]/span[@class="rating_num"]/text()' ).extract()[0] item['score_num'] = movie.xpath( './/div[@class="star"]/span/text()').re(ur'(\d+)人评价')[0] yield item
更改后的代码中并没有start_urls,而又多了start_requests,二者的作用可以见Scrapy(一)。简单来说使用start_requests使我们对于初始URL处理有了更多的权利,比如这次给初始URL增加请求头User-Agent
再次运行爬虫,就可以看到我们需要的信息都被下载到douban.csv中了。
5、自动翻页
然而我们只能爬取到该页的内容,那么如何把剩下的也一起爬下来呢?实现自动翻页一般有两种方法:
①、在页面中找到下一页的地址;
②、根据URL变化规律构造所有页面地址。
一般情况下采用第一种方法,第二种方法适用于下一页地址为JS加载的情况。今天我们只说第一种方法。
首先,我们利用Chrome开发者模式找到下一页的地址:
<span class="next"> <link rel="next" href="?start=25&filter="> <a href="?start=25&filter=">后页></a> </span>
然后在解析该页面时获取下一页的地址并将地址交给Scheduler,方法,在上文代码中的parse方法最后补充语句:
next_url=response.xpath('//span[@class="next"]/a/@href').extract() if next_url: next_url = 'https://movie.douban.com/top250' + next_url[0] yield Request(next_url, headers=self.headers)
最终完成的代码为:
from scrapy import Request from scrapy import Spider from S.items import DoubanMovieItem class DoubanMovieTop250Spider(Spider): name='douban_movie_top250' headers={ 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', } def start_requests(self): url='https://movie.douban.com/top250' yield Request(url,headers=self.headers) def parse(self,response): item=DoubanMovieItem() movies=response.xpath('//ol[@class="grid_view"]/li') for movie in movies: item['ranking'] = movie.xpath( './/div[@class="pic"]/em/text()').extract()[0] item['name'] = movie.xpath( './/div[@class="hd"]/a/span[1]/text()').extract()[0] item['score'] = movie.xpath( './/div[@class="star"]/span[@class="rating_num"]/text()' ).extract()[0] item['num'] = movie.xpath( './/div[@class="star"]/span/text()').re(r'(\d+)人评价')[0] yield item next_url=response.xpath('//span[@class="next"]/a/@href').extract() if next_url: next_url = 'https://movie.douban.com/top250' + next_url[0] yield Request(next_url, headers=self.headers)
6、总结
构建Scrapy爬虫的一般性语句
①创建项目、爬虫
scrapy startproject xxx
cd xxx
scrapy genspider xxxs url
②items.py:为提取要素创建承接变量
每个要提取的要素都要有一个承接对象,均是在Item类下通过scrapy.Field()方法创建:
#items.py
import scrapy
class xxx_Item(scrapy.item):
attr1=scrapy.Field()
attr2=scrapy.Field()
……
③setting.py:一些配置
主要设置项有以下三个,具体用途见Scrapy(一)中所写
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 1
DEFAULT_REQUEST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36'
}
④pipelines.py:管道
当需要多个管道的时候,在这个文件夹中添加(一般用不到)
⑤spiders/xxx.py:爬虫
step1、导入模块、类
from scrapy import Request
from scrapy import Spider
from xxx.items import xxx_Item
step2、spider类属性
class TxsSpider(scrapy.Spider):
name = 'txs'
allowed_domains = ['v.qq.com']
start_urls = ['https://v.qq.com/channel/movie']
其实这一部分,在通过genspider创建爬虫文件时已经自动生成了,唯一需要我们写的是start_urls,这是我们进行爬取的起始页
step3、spider类方法
主要是parse,在其中通过XPath提取所需项:
def parse(self,response): item=xxx_Item() #items.py引入的Item类 movies=response.xpath('...') #提取所有的影片 xpath函数参数即为xpath路径表达式 for movie in movies: item['attr_1'] = movie.xpath('.//...').extract()[0] #注意路径开头的点. !!! item['attr_2'] = movie.xpath('.//...').extract()[0] ... #如果只需要提取部分文本,除了在xpath中用text()函数外,还要用正则表达式匹配 item['attr_n'] = movie.xpath('.//.../text()').re(r'...') yield item #每提取一个item,就要记录一个,用yield item构造迭代器 #如果需要翻页,就用下文的代码进行翻页 #方法①,直接通过页面中展示出来的“下一页”链接进行 #链接要素一般在属性href中 next_url=response.xpath('.../@href').extract() if next_url: #如果next_url存在时,就根据页码变换公式构造下页URL next_url= '基本url' + next_url[0] yield Request(next_url,headers=self.headers)#请求下一页URL,重复爬取直至结束 #方法②,通过观察每一页的URL,发现其中规律,根据规律构造URL if self.offset < 120: self.offset += 30 next_url = 'https://v.qq.com/x/bu/pagesheet/list?append=1&channel=cartoon&iarea=1&listpage=2&offset={}&pagesize=30'.format( str(self.offset)) yield Request(url=url,header=self.header)
⑥运行爬虫
scrapy crawl 爬虫名 -o 文件 #追加写 scrapy crawl 爬虫名 -0 文件 #覆盖写 大写字母而非数字
将爬取到的内容(即⑤中的item)保存为csv文件
如果按照以上①-⑥执行,就可以基本实现爬虫功能;如果想进一步优化,还有其他一些可选项,优化项见⑦-⑨
⑦pipelines.py:管道
管道可以处理提取的数据,比如存数据库等,这里只展示如何进行输出:
from itemadapter import ItemAdapter class TxPipeline: def process_item(self, item, spider): print (item) return item
处理项是item,只需在process_item中写对item进行处理的代码就行了。
⑧在Python中运行;在Python中写代码,使之可以起到与在cmd运行代码相同的作用:
from scrapy import cmdline cmdline.execute('scrapy crawl txs -o tx.csv'.split())
⑨多线程爬取
以上所说爬取过程,都是顺序执行的,即总是前一页先输出,后一页后输出。不适合处理数据量较大的情况,一个好的方式是采用多线程方法,这里的多线程是基于方法的多线程,不是通过创建Thread对象来实现。具体说来,是一次性把请求交给Scheduler。
我们通过重写start_requests方法来实现我们的想法:
#txs.py def start_requests(self): for i in range(4):#假设要爬取4页 url=new_url(i)#根据各页URL规律构建各页的URL yield Request(url,callback=self.parse) def parse(self,response): #删去翻页查询的代码,其他照旧
所以多线程查询的代码,总的应该为:
def start_requests(self): for i in range(4): url=new_url(i) #每页URL的构造 yield Request(url,callback=self.parse) def parse(self,response): item=xxx_Item() #items.py引入的Item类 movies=response.xpath('...') #提取所有的影片 xpath函数参数即为xpath路径表达式 for movie in movies: item['attr_1'] = movie.xpath('.//...').extract()[0] #注意路径开头的点. !!! item['attr_2'] = movie.xpath('.//...').extract()[0] ... #如果只需要提取部分文本,除了在xpath中用text()函数外,还要用正则表达式匹配 item['attr_n'] = movie.xpath('.//.../text()').re(r'...') yield item #每提取一个item,就要记录一个,用yield item构造迭代器 str(self.offset)) yield Request(url=url,header=self.header)
⑩流程梳理
新建项目——>新建爬虫文件——>明确爬取的内容,写Item——>写爬虫程序——>交给写pipeline数据处理程序——>配置setting——>执行爬虫(①cmd;②程序中写run程序)
7、遇到的问题及解决
①、输出为CSV文件时只输出了最后一项
原因:step3中的yield item放在了for循环以外,导致只输出了一次
解决方法:step3中的yield item放在for循环最后
②、能通过response.xpath找到大的要素,但是通过for循环movie.xpath找具体小要素时却找不到
原因:movie.xpath的XPath路径表达式写的过程中,并没有加前缀.,因此就导致了找小要素时,并不是从之前已经找好的路径开始的,而是又从头开始了
解决方法:step3中写movie.xpath中的XPath表达式时,在路径最前加 .,表示从当前目录开始(具体见XPath - ShineLe - 博客园)
③、大要素的xpath写好了,但是却并没有找到大要素
原因:部分大要素的属性中有空格,这些空格绝对不能省略
8、补充
①yield
yield是生成器(Generator)的标志,关于生成器的内容,可以看列表生成式 生成器 迭代器 yield - ShineLe - 博客园,简单来说,yield类似于return,区别在于yield会保留现场,当下次执行循环时,不会从头开始,而是从上次结束(即yield处)的地方开始。
Scrapy爬虫框架中关于这一部分的理解我觉得非常好,现引用至此——在本程序中,第一个yield,我们对item封装数据后,就调用yield将控制权移交给pipeline,pipeline拿到item处理后继续返回该程序。
第二个yield,程序中用到了回调(callback)机制,回调的对象是parse,即当前方法,通过不断回调,程序可以不间断地处理URL中包含的下一个URL。但要设置终止条件,不然可能会陷入死循环。
②XPath内容提取——extract
还有一个要注意的是,我们如何提取XPath中的数据,写法有多种:
item['name']=i.xpath('./a/@title')[0]#①拿到原数据,其中可能有我们用不到的东西 items['name']=i.xpath('./a/@title').extract()#②将原数据转化为字符串 items['name']=i.xpath('./a/@title').extract_first()#③拿到字符串中第一个数据,即我们要的数据; items['name']=i.xpath('./a/@title').get()#④ items['name']=i.xpath('./a/@title').extract()[0]#⑤ #③④⑤作用相同,源代码中我们用的是⑤,其实都可以
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性