Python抓取框架Scrapy爬虫入门:页面提取


Posted in Python onDecember 01, 2017

前言

Scrapy是一个非常好的抓取框架,它不仅提供了一些开箱可用的基础组建,还能够根据自己的需求,进行强大的自定义。本文主要给大家介绍了关于Python抓取框架Scrapy之页面提取的相关内容,分享出来供大家参考学习,下面随着小编来一起学习学习吧。

在开始之前,关于scrapy框架的入门大家可以参考这篇文章:https://3water.com/article/87820.htm

下面创建一个爬虫项目,以图虫网为例抓取图片。

一、内容分析

打开 图虫网,顶部菜单“发现” “标签”里面是对各种图片的分类,点击一个标签,比如“美女”,网页的链接为:https://tuchong.com/tags/美女/,我们以此作为爬虫入口,分析一下该页面:

打开页面后出现一个个的图集,点击图集可全屏浏览图片,向下滚动页面会出现更多的图集,没有页码翻页的设置。Chrome右键“检查元素”打开开发者工具,检查页面源码,内容部分如下:

<div class="content">
 <div class="widget-gallery">
 <ul class="pagelist-wrapper">
  <li class="gallery-item...

可以判断每一个li.gallery-item是一个图集的入口,存放在ul.pagelist-wrapper下,div.widget-gallery是一个容器,如果使用 xpath 选取应该是://div[@class="widget-gallery"]/ul/li,按照一般页面的逻辑,在li.gallery-item下面找到对应的链接地址,再往下深入一层页面抓取图片。

但是如果用类似 Postman 的HTTP调试工具请求该页面,得到的内容是:

<div class="content">
 <div class="widget-gallery"></div>
</div>

也就是并没有实际的图集内容,因此可以断定页面使用了Ajax请求,只有在浏览器载入页面时才会请求图集内容并加入div.widget-gallery中,通过开发者工具查看XHR请求地址为:

https://tuchong.com/rest/tags/美女/posts?page=1&count=20&order=weekly&before_timestamp=

参数很简单,page是页码,count是每页图集数量,order是排序,before_timestamp为空,图虫因为是推送内容式的网站,因此before_timestamp应该是一个时间值,不同的时间会显示不同的内容,这里我们把它丢弃,不考虑时间直接从最新的页面向前抓取。

请求结果为JSON格式内容,降低了抓取难度,结果如下:

{
 "postList": [
 {
 "post_id": "15624611",
 "type": "multi-photo",
 "url": "https://weishexi.tuchong.com/15624611/",
 "site_id": "443122",
 "author_id": "443122",
 "published_at": "2017-10-28 18:01:03",
 "excerpt": "10月18日",
 "favorites": 4052,
 "comments": 353,
 "rewardable": true,
 "parent_comments": "165",
 "rewards": "2",
 "views": 52709,
 "title": "微风不燥 秋意正好",
 "image_count": 15,
 "images": [
 {
  "img_id": 11585752,
  "user_id": 443122,
  "title": "",
  "excerpt": "",
  "width": 5016,
  "height": 3840
 },
 {
  "img_id": 11585737,
  "user_id": 443122,
  "title": "",
  "excerpt": "",
  "width": 3840,
  "height": 5760
 },
 ...
 ],
 "title_image": null,
 "tags": [
 {
  "tag_id": 131,
  "type": "subject",
  "tag_name": "人像",
  "event_type": "",
  "vote": ""
 },
 {
  "tag_id": 564,
  "type": "subject",
  "tag_name": "美女",
  "event_type": "",
  "vote": ""
 }
 ],
 "favorite_list_prefix": [],
 "reward_list_prefix": [],
 "comment_list_prefix": [],
 "cover_image_src": "https://photo.tuchong.com/443122/g/11585752.webp",
 "is_favorite": false
 }
 ],
 "siteList": {...},
 "following": false,
 "coverUrl": "https://photo.tuchong.com/443122/ft640/11585752.webp",
 "tag_name": "美女",
 "tag_id": "564",
 "url": "https://tuchong.com/tags/%E7%BE%8E%E5%A5%B3/",
 "more": true,
 "result": "SUCCESS"
}

根据属性名称很容易知道对应的内容含义,这里我们只需关心 postlist 这个属性,它对应的一个数组元素便是一个图集,图集元素中有几项属性我们需要用到:

  • url:单个图集浏览的页面地址
  • post_id:图集编号,在网站中应该是唯一的,可以用来判断是否已经抓取过该内容
  • site_id:作者站点编号 ,构建图片来源链接要用到
  • title:标题
  • excerpt:摘要文字
  • type:图集类型,目前发现两种,一种multi-photo是纯照片,一种text是文字与图片混合的文章式页面,两种内容结构不同,需要不同的抓取方式,本例中只抓取纯照片类型,text类型直接丢弃
  • tags:图集标签,有多个
  • image_count:图片数量
  • images:图片列表,它是一个对象数组,每个对象中包含一个img_id属性需要用到

根据图片浏览页面分析,基本上图片的地址都是这种格式: https://photo.tuchong.com/{site_id}/f/{img_id}.jpg ,很容易通过上面的信息合成。

二、创建项目

  • 进入cmder命令行工具,输入workon scrapy 进入之前建立的虚拟环境,此时命令行提示符前会出现(Scrapy) 标识,标识处于该虚拟环境中,相关的路径都会添加到PATH环境变量中便于开发及使用。
  • 输入 scrapy startproject tuchong 创建项目 tuchong
  • 进入项目主目录,输入 scrapy genspider photo tuchong.com 创建一个爬虫名称叫 photo (不能与项目同名),爬取 tuchong.com 域名(这个需要修改,此处先输个大概地址),的一个项目内可以包含多个爬虫

经过以上步骤,项目自动建立了一些文件及设置,目录结构如下:

(PROJECT)
│ scrapy.cfg
│
└─tuchong
 │ items.py
 │ middlewares.py
 │ pipelines.py
 │ settings.py
 │ __init__.py
 │
 ├─spiders
 │ │ photo.py
 │ │ __init__.py
 │ │
 │ └─__pycache__
 │  __init__.cpython-36.pyc
 │
 └─__pycache__
  settings.cpython-36.pyc
  __init__.cpython-36.pyc
  • scrapy.cfg:基础设置
  • items.py:抓取条目的结构定义
  • middlewares.py:中间件定义,此例中无需改动
  • pipelines.py:管道定义,用于抓取数据后的处理
  • settings.py:全局设置
  • spiders\photo.py:爬虫主体,定义如何抓取需要的数据

三、主要代码

items.py 中创建一个TuchongItem类并定义需要的属性,属性继承自 scrapy.Field 值可以是字符、数字或者列表或字典等等:

import scrapy

class TuchongItem(scrapy.Item):
 post_id = scrapy.Field()
 site_id = scrapy.Field()
 title = scrapy.Field()
 type = scrapy.Field()
 url = scrapy.Field()
 image_count = scrapy.Field()
 images = scrapy.Field()
 tags = scrapy.Field()
 excerpt = scrapy.Field()
 ...

这些属性的值将在爬虫主体中赋予。

spiders\photo.py 这个文件是通过命令 scrapy genspider photo tuchong.com 自动创建的,里面的初始内容如下:

import scrapy

class PhotoSpider(scrapy.Spider):
 name = 'photo'
 allowed_domains = ['tuchong.com']
 start_urls = ['http://tuchong.com/']

 def parse(self, response):
 pass

爬虫名 name,允许的域名 allowed_domains(如果链接不属于此域名将丢弃,允许多个) ,起始地址 start_urls 将从这里定义的地址抓取(允许多个)

函数 parse 是处理请求内容的默认回调函数,参数 response 为请求内容,页面内容文本保存在 response.body 中,我们需要对默认代码稍加修改,让其满足多页面循环发送请求,这需要重载 start_requests 函数,通过循环语句构建多页的链接请求,修改后代码如下:

import scrapy, json
from ..items import TuchongItem

class PhotoSpider(scrapy.Spider):
 name = 'photo'
 # allowed_domains = ['tuchong.com']
 # start_urls = ['http://tuchong.com/']

 def start_requests(self):
 url = 'https://tuchong.com/rest/tags/%s/posts?page=%d&count=20&order=weekly';
 # 抓取10个页面,每页20个图集
 # 指定 parse 作为回调函数并返回 Requests 请求对象
 for page in range(1, 11):
  yield scrapy.Request(url=url % ('美女', page), callback=self.parse)

 # 回调函数,处理抓取内容填充 TuchongItem 属性
 def parse(self, response):
 body = json.loads(response.body_as_unicode())
 items = []
 for post in body['postList']:
  item = TuchongItem()
  item['type'] = post['type']
  item['post_id'] = post['post_id']
  item['site_id'] = post['site_id']
  item['title'] = post['title']
  item['url'] = post['url']
  item['excerpt'] = post['excerpt']
  item['image_count'] = int(post['image_count'])
  item['images'] = {}
  # 将 images 处理成 {img_id: img_url} 对象数组
  for img in post.get('images', ''):
  img_id = img['img_id']
  url = 'https://photo.tuchong.com/%s/f/%s.jpg' % (item['site_id'], img_id)
  item['images'][img_id] = url

  item['tags'] = []
  # 将 tags 处理成 tag_name 数组
  for tag in post.get('tags', ''):
  item['tags'].append(tag['tag_name'])
  items.append(item)
 return items

经过这些步骤,抓取的数据将被保存在 TuchongItem 类中,作为结构化的数据便于处理及保存。

前面说过,并不是所有抓取的条目都需要,例如本例中我们只需要 type="multi_photo 类型的图集,并且图片太少的也不需要,这些抓取条目的筛选操作以及如何保存需要在pipelines.py中处理,该文件中默认已创建类 TuchongPipeline 并重载了 process_item 函数,通过修改该函数只返回那些符合条件的 item,代码如下:

...
 def process_item(self, item, spider):
 # 不符合条件触发 scrapy.exceptions.DropItem 异常,符合条件的输出地址
 if int(item['image_count']) < 3:
  raise DropItem("美女太少: " + item['url'])
 elif item['type'] != 'multi-photo':
  raise DropItem("格式不对: " + + item['url'])
 else:
  print(item['url'])
 return item
...

当然如果不用管道直接在 parse 中处理也是一样的,只不过这样结构更清晰一些,而且还有功能更多的FilePipelines和ImagePipelines可供使用,process_item将在每一个条目抓取后触发,同时还有 open_spider 及 close_spider 函数可以重载,用于处理爬虫打开及关闭时的动作。

注意:管道需要在项目中注册才能使用,在 settings.py 中添加:

ITEM_PIPELINES = {
 'tuchong.pipelines.TuchongPipeline': 300, # 管道名称: 运行优先级(数字小优先)
}

另外,大多数网站都有反爬虫的 Robots.txt 排除协议,设置 ROBOTSTXT_OBEY = True 可以忽略这些协议,是的,这好像只是个君子协定。如果网站设置了浏览器User Agent或者IP地址检测来反爬虫,那就需要更高级的Scrapy功能,本文不做讲解。

四、运行

返回 cmder 命令行进入项目目录,输入命令:

scrapy crawl photo

终端会输出所有的爬行结果及调试信息,并在最后列出爬虫运行的统计信息,例如:

[scrapy.statscollectors] INFO: Dumping Scrapy stats:
{'downloader/request_bytes': 491,
 'downloader/request_count': 2,
 'downloader/request_method_count/GET': 2,
 'downloader/response_bytes': 10224,
 'downloader/response_count': 2,
 'downloader/response_status_count/200': 2,
 'finish_reason': 'finished',
 'finish_time': datetime.datetime(2017, 11, 27, 7, 20, 24, 414201),
 'item_dropped_count': 5,
 'item_dropped_reasons_count/DropItem': 5,
 'item_scraped_count': 15,
 'log_count/DEBUG': 18,
 'log_count/INFO': 8,
 'log_count/WARNING': 5,
 'response_received_count': 2,
 'scheduler/dequeued': 1,
 'scheduler/dequeued/memory': 1,
 'scheduler/enqueued': 1,
 'scheduler/enqueued/memory': 1,
 'start_time': datetime.datetime(2017, 11, 27, 7, 20, 23, 867300)}

主要关注ERROR及WARNING两项,这里的 Warning 其实是不符合条件而触发的 DropItem 异常。

五、保存结果

大多数情况下都需要对抓取的结果进行保存,默认情况下 item.py 中定义的属性可以保存到文件中,只需要命令行加参数 -o {filename} 即可:

scrapy crawl photo -o output.json # 输出为JSON文件
scrapy crawl photo -o output.csv # 输出为CSV文件

注意:输出至文件中的项目是未经过 TuchongPipeline 筛选的项目,只要在 parse 函数中返回的 Item 都会输出,因此也可以在 parse 中过滤只返回需要的项目
如果需要保存至数据库,则需要添加额外代码处理,比如可以在 pipelines.py 中 process_item 后添加:

...
 def process_item(self, item, spider):
  ...
  else:
   print(item['url'])
   self.myblog.add_post(item) # myblog 是一个数据库类,用于处理数据库操作
  return item
...

为了在插入数据库操作中排除重复的内容,可以使用 item['post_id'] 进行判断,如果存在则跳过。
本项目中的抓取内容只涉及了文本及图片链接,并未下载图片文件,如需下载图片,可以通过两种方式:

安装 Requests 模块,在 process_item 函数中下载图片内容,同时在保存数据库时替换为本地图片路径。
使用 ImagePipelines 管道下载图片,具体使用方法下回讲解。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Python 相关文章推荐
Python中optionParser模块的使用方法实例教程
Aug 29 Python
Python执行时间的计算方法小结
Mar 17 Python
python微信跳一跳系列之自动计算跳一跳距离
Feb 26 Python
python 实现调用子文件下的模块方法
Dec 07 Python
python 实现交换两个列表元素的位置示例
Jun 26 Python
Python 时间戳之获取整点凌晨时间戳的操作方法
Jan 28 Python
python3 deque 双向队列创建与使用方法分析
Mar 24 Python
Python Selenium截图功能实现代码
Apr 26 Python
PyTorch中model.zero_grad()和optimizer.zero_grad()用法
Jun 24 Python
浅谈如何使用python抓取网页中的动态数据实现
Aug 17 Python
python如何控制进程或者线程的个数
Oct 16 Python
Python数据可视化常用4大绘图库原理详解
Oct 23 Python
Python实现调度算法代码详解
Dec 01 #Python
Python进阶学习之特殊方法实例详析
Dec 01 #Python
Python用户推荐系统曼哈顿算法实现完整代码
Dec 01 #Python
浅谈python 里面的单下划线与双下划线的区别
Dec 01 #Python
vscode 远程调试python的方法
Dec 01 #Python
Python中单、双下划线的区别总结
Dec 01 #Python
从CentOS安装完成到生成词云python的实例
Dec 01 #Python
You might like
PHP memcache扩展的三种安装方法
2009/04/26 PHP
php自定义函数之递归删除文件及目录
2010/08/08 PHP
php下正则来匹配dede模板标签的代码
2010/08/21 PHP
php数组函数序列之each() - 获取数组当前内部指针所指向元素的键名和键值,并将指针移到下一位
2011/10/31 PHP
深入PHP许愿墙模块功能分析
2013/06/25 PHP
PHP实现图片不变型裁剪及图片按比例裁剪的方法
2016/01/14 PHP
完美解决phpdoc导出文档中@package的warning及Error的错误
2016/05/17 PHP
Apache PHP MySql安装配置图文教程
2016/08/27 PHP
jquery中页面Ajax方法$.load的功能使用介绍
2014/10/20 Javascript
jQuery中:image选择器用法实例
2015/01/03 Javascript
JQuery限制复选框checkbox可选中个数的方法
2015/04/20 Javascript
简单实现兼容各大浏览器的js复制内容到剪切板
2015/09/09 Javascript
IE9+已经不对document.createElement向下兼容的解决方法
2015/09/14 Javascript
基于JS实现网页中的选项卡(两种方法)
2017/06/16 Javascript
Vue实现数据请求拦截
2019/10/23 Javascript
使用webpack搭建vue环境的教程详解
2019/12/31 Javascript
[03:24]CDEC.Y赛前采访 努力备战2016国际邀请赛中国区预选赛
2016/06/25 DOTA
python数据预处理之将类别数据转换为数值的方法
2017/07/05 Python
Python实现将照片变成卡通图片的方法【基于opencv】
2018/01/17 Python
Python+Tensorflow+CNN实现车牌识别的示例代码
2019/10/11 Python
浅析PEP572: 海象运算符
2019/10/15 Python
flask的orm框架SQLAlchemy查询实现解析
2019/12/12 Python
Python迷宫生成和迷宫破解算法实例
2019/12/24 Python
Python使用循环神经网络解决文本分类问题的方法详解
2020/01/16 Python
jupyter notebook 调用环境中的Keras或者pytorch教程
2020/04/14 Python
python实现小程序推送页面收录脚本
2020/04/20 Python
e路東瀛(JAPANiCAN)香港:日本旅游、日本酒店和温泉旅馆预订
2018/11/21 全球购物
AURALog面试题软件测试方面
2013/10/22 面试题
一百多行代码实现react拖拽hooks
2021/03/23 Javascript
关于读书的演讲稿500字
2014/08/27 职场文书
信息合作协议书
2014/10/09 职场文书
2014年医药代表工作总结
2014/11/22 职场文书
个人党性分析材料
2014/12/19 职场文书
个人优缺点总结
2015/02/28 职场文书
go设置多个GOPATH的方式
2021/05/05 Golang
python图片灰度化处理的几种方法
2021/06/23 Python