学习自: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. git init和git init –bare的区别:

    感谢原文作者:ljchlx 原文链接:https://blog.csdn.net/ljchlx/article/details/21805231 git init 和 git init –bare 的 ...

  2. Android App发布遇到的问题总结【转】

    感谢大佬:https://www.cnblogs.com/jeffen/p/6824722.html   问题描述(v1和v2) Android 7.0中引入了APK Signature Scheme ...

  3. JVM学习八-(复习)年轻代、老年代、永久代

    Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象,如下图所示: 在 Java 中,堆被划分成两个不同的区域:新生代 ( Young ).老年代 ( Old).新生代 ...

  4. 有了Autolayout的UILabel

    在没有Autolayout之前,UILabel的文字内容总是居中显示,导致顶部和底部会有一大片空缺区域 有Autolayout之后,UILabel的bounds默认会自动包住所有的文字内容,顶部和底部 ...

  5. MySQL手写代码相关变量

    原创:转载需注明原创地址 https://www.cnblogs.com/fanerwei222/p/11777682.html 手写一些SQL代码时候需要用到的关键字. DELIMITER, BEG ...

  6. LAMP以及各组件的编译安装

    LAMP以及各组件的编译安装 目录 LAMP以及各组件的编译安装 一.LAMP 1. LAMP概述 2. 各组件的主要作用 3. 平台环境的安装顺序 二.编译安装apache httpd 1. 关闭防 ...

  7. 《PHP程序员面试笔试宝典》——如何应对自己不会回答的问题?

    如何巧妙地回答面试官的问题? 本文摘自<PHP程序员面试笔试宝典> 在面试的过程中,对面试官提出的问题求职者并不是都能回答出来,计算机技术博大精深,很少有人能对计算机技术的各个分支学科了如 ...

  8. Solution -「ARC 104D」Multiset Mean

    \(\mathcal{Description}\)   Link.   读题时间≈想题时间,草.(   给定 \(N,K,M\),对于每个 \(x\in[1,N]\) 的整数 \(x\),统计多重集 ...

  9. DBLink的使用(从A库使用SQL查询B库的数据)

    DBLink的使用 情景:今天我需要从A数据库查询B数据库的数据,进行一些数据比对和联合查询的操作. 所以用到的DBLink,在此记录一下使用流程,希望能够帮助下一个小白,一步到位的解决问题. 一句话 ...

  10. Python+selenium自动循环送贺卡

    Python源代码如下: # coding=utf-8 from selenium import webdriver from time import sleep from random import ...