学习自: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">&nbsp;/&nbsp;The Shawshank Redemption</span>
<span class="other">&nbsp;/&nbsp;月黑高飞(港) / 刺激1995(台)</span>
</a> <span class="playable">[可播放]</span>
</div>
<div class="bd">
<p class="">
导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
</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&amp;filter=">
<a href="?start=25&amp;filter=">后页&gt;</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]#⑤
#③④⑤作用相同,源代码中我们用的是⑤,其实都可以

Python:Scrapy(二) 实例分析与总结、写一个爬虫的一般步骤的更多相关文章

  1. Python正则简单实例分析

    Python正则简单实例分析 本文实例讲述了Python正则简单用法.分享给大家供大家参考,具体如下: 悄悄打入公司内部UED的一个Python爱好者小众群,前两天一位牛人发了条消息: 小的测试题:  ...

  2. 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话

    wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来源 在掘金看到了一篇<用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件>后, 在评论区偶然 ...

  3. python多线程同步实例分析

    进程之间通信与线程同步是一个历久弥新的话题,对编程稍有了解应该都知道,但是细说又说不清.一方面除了工作中可能用的比较少,另一方面就是这些概念牵涉到的东西比较多,而且相对较深.网络编程,服务端编程,并发 ...

  4. React学习及实例开发(二)——用Ant Design写一个简单页面

    本文基于React v16.4.1 初学react,有理解不对的地方,欢迎批评指正^_^ 一.引入Ant Design 1.安装antd yarn add antd 2.引入 react-app-re ...

  5. Extjs6(二)——用extjs6.0写一个系统登录及注销

    本文基于ext-6.0.0 一.写login页 1.在view文件夹中创建login文件夹,在login中创建文件login.js和loginController.js(login.js放在class ...

  6. Python之小测试:用正则表达式写一个小爬虫用于保存贴吧里的所有图片

    很简单的两步: 1.获取网页源代码 2.利用正则表达式提取出图片地址 3.下载 #!/usr/bin/python #coding=utf8 import re # 正则表达式 import urll ...

  7. 用python写一个爬虫——爬取性感小姐姐

    忍着鼻血写代码 今天写一个简单的网上爬虫,爬取一个叫妹子图的网站里面所有妹子的图片. 然后试着先爬取了三页,大概有七百多张图片吧!各个诱人的很,有兴趣的同学可以一起来爬一下,大佬级程序员勿喷,简单爬虫 ...

  8. python多进程通信实例分析

    操作系统会为每一个创建的进程分配一个独立的地址空间,不同进程的地址空间是完全隔离的,因此如果不加其他的措施,他们完全感觉不到彼此的存在.那么进程之间怎么进行通信?他们之间的关联是怎样的?实现原理是什么 ...

  9. python类型转换convert实例分析

    在python的开发过程中,难免会遇到类型转换,这里给出常见的类型转换demo: 类型 说明 int(x [,base ]) 将x转换为一个整数 long(x [,base ]) 将x转换为一个长整数 ...

随机推荐

  1. 【程序6】用*号输出字母C的图案

    我自己写的 print(' * * *\n'); print(' * \n'); print(' * \n'); print(' * * * *\n'); 官方答案 print('*' * 10) f ...

  2. Python中的魔术方法

    什么是魔术方法? 在Python中,所有用"__"包起来的方法,都称为[魔术方法]. 魔术方法一般是为了让显示器调用的,你自己并不需要调用它们. __init__:初始化函数 这个 ...

  3. Java 中对象锁和类锁的区别? 关键字 Synchronized的用法?

    一  对象锁和类锁的关系 /* * 对象锁和[类锁] 全局锁的关系? 对象锁是用于对象实例方法,或者一个对象实例上的 this 类锁是用于类的静态方法或者一个类的class对象上的. Ag.class ...

  4. django之集成阿里云通信(发送手机短信验证码)

    python3 + django2.0 集成 "阿里云通信" 服务: (SDK文档地址:https://help.aliyun.com/document_detail/55491. ...

  5. docker常用命令、镜像命令、容器命令、数据卷,使用dockerFile创建镜像,dockefile的语法规则。

    一.docker常用命令? 1. 常用帮助命令 1.1 docker的信息以及版本号 /* docker info 查看docker的信息 images2 docker本身就是一个镜像. docker ...

  6. Linux 常见文件管理命令

    Linux文件系统 根目录:/ 从根目录开始,下面有一堆小目录 root:根用户的目录 bin:可执行文件命令 etc:配置文件 var:日志 lib:安装包或头文件,库文件 home:所有用户的家目 ...

  7. 从我做起[原生DI实现模块化和批量注入].Net Core 之一

    实现模块化注册 .Net Core实现模块化批量注入 我将新建一个项目从头开始项目名称Sukt.Core. 该项目分层如下: Sukt.Core.API 为前端提供APi接口(里面尽量不存在业务逻辑, ...

  8. div置顶

    转载请注明来源:https://www.cnblogs.com/hookjc/ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transit ...

  9. html路径

    一.HTML 相对路径和绝对路径区别分析 HTML初学者会经常遇到这样一个问题,如何正确引用一个文件.比如,怎样在一个HTML网页中引用另外一个HTML网页作为超链接(hyperlink)?怎样在一个 ...

  10. 实现“手机qq”侧滑菜单 -- 吴欧

    基本数据采集 经过体验,手机QQ采用的应该是线性动画,即视图缩放比例等随手指在屏幕上滑动的距离以一次方程的形式变化. 提取基本数据,向右侧滑达到最大幅度时: 1.   右侧主视图左边界距离屏幕左边界的 ...