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执行系统命令后获取返回值的几种方式集合
May 12 Python
Python json模块dumps、loads操作示例
Sep 06 Python
pandas数据集的端到端处理
Feb 18 Python
python 日期排序的实例代码
Jul 11 Python
浅谈Python2之汉字编码为unicode的问题(即类似\xc3\xa4)
Aug 12 Python
python pyenv多版本管理工具的使用
Dec 23 Python
详解Python实现进度条的4种方式
Jan 15 Python
Python 使用 environs 库定义环境变量的方法
Feb 25 Python
TensorFlow保存TensorBoard图像操作
Jun 23 Python
Python3爬虫中Selenium的用法详解
Jul 10 Python
python中pathlib模块的基本用法与总结
Aug 17 Python
python glom模块的使用简介
Apr 13 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分多步骤填写发布信息的简单方法实例代码
2012/09/23 PHP
php过滤敏感词的示例
2014/03/31 PHP
两款万能的php分页类
2015/11/12 PHP
javascript 学习之旅 (2)
2009/02/05 Javascript
基于jquery封装的一个js分页
2011/11/15 Javascript
百度地图api应用标注地理位置信息(js版)
2013/02/01 Javascript
向左滚动文字 js代码效果
2013/08/17 Javascript
实例讲解JavaScript中的this指向错误解决方法
2016/06/13 Javascript
AngularJS优雅的自定义指令
2016/07/01 Javascript
JavaScript 闭包详细介绍
2016/09/28 Javascript
详解AngularJS1.6版本中ui-router路由中/#!/的解决方法
2017/05/22 Javascript
JS 判断某变量是否为某数组中的一个值的3种方法(总结)
2017/07/10 Javascript
javaScript封装的各种写法
2017/08/14 Javascript
JS实现非首屏图片延迟加载的示例
2018/01/06 Javascript
bmob js-sdk 在vue中的使用教程
2018/01/21 Javascript
layui点击按钮添加可编辑的一行方法
2018/08/15 Javascript
解决vue 单文件组件中样式加载问题
2019/04/24 Javascript
JS正则表达式封装与使用操作示例
2019/05/15 Javascript
使用 node.js 模仿 Apache 小部分功能
2019/07/07 Javascript
Vue如何基于vue-i18n实现多国语言兼容
2020/07/17 Javascript
vue实现选中效果
2020/10/07 Javascript
解决vant的Toast组件时提示not defined的问题
2020/11/11 Javascript
vue print.js打印支持Echarts图表操作
2020/11/13 Javascript
Python使用剪切板的方法
2017/06/06 Python
详解supervisor使用教程
2017/11/21 Python
Python实现接受任意个数参数的函数方法
2018/04/21 Python
python2.7实现爬虫网页数据
2018/05/25 Python
Windows+Anaconda3+PyTorch+PyCharm的安装教程图文详解
2020/04/03 Python
财产保全担保书
2015/01/20 职场文书
公司停电通知
2015/04/15 职场文书
西安事变观后感
2015/06/12 职场文书
甜美蛋糕店的创业计划书模板,拿来即用!
2019/08/21 职场文书
导游词之唐山景点
2019/12/18 职场文书
Python 批量下载阴阳师网站壁纸
2021/05/19 Python
Python 内置函数速查表一览
2021/06/02 Python
「Manga Time Kirara MAX」2022年5月号封面公开
2022/03/21 日漫