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赋值语句后逗号的作用分析
Jun 08 Python
在Python的Django框架中显示对象子集的方法
Jul 21 Python
python中使用序列的方法
Aug 03 Python
Python中使用插入排序算法的简单分析与代码示例
May 04 Python
Python实现PS滤镜中马赛克效果示例
Jan 20 Python
小白如何入门Python? 制作一个网站为例
Mar 06 Python
python3.x 将byte转成字符串的方法
Jul 17 Python
Python 比较文本相似性的方法(difflib,Levenshtein)
Oct 15 Python
对Python中for复合语句的使用示例讲解
Nov 01 Python
python批量下载网站马拉松照片的完整步骤
Dec 05 Python
Flask教程之重定向与错误处理实例分析
Aug 01 Python
Python Numpy 控制台完全输出ndarray的实现
Feb 19 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通过COM类调用组件的实现代码
2012/01/11 PHP
php限制上传文件类型并保存上传文件的方法
2015/03/13 PHP
php实现可逆加密的方法
2015/08/11 PHP
如何解决PHP无法实现多线程的问题
2015/09/25 PHP
PHP删除数组中指定值的元素常用方法实例分析【4种方法】
2018/08/21 PHP
javascript编程起步(第六课)
2007/01/10 Javascript
jquery 常用操作方法
2010/01/28 Javascript
jquery的冒泡事件的阻止与允许(三种实现方法)
2013/02/01 Javascript
jquery struts 验证唯一标识(公用方法)
2013/03/27 Javascript
display和visibility的区别示例介绍
2014/02/26 Javascript
node.js中的fs.appendFile方法使用说明
2014/12/17 Javascript
基于JQuery打造无缝滚动新闻步骤详解
2016/03/31 Javascript
基于JS实现类似支付宝支付密码输入框
2016/09/02 Javascript
浅谈NodeJs之数据库异常处理
2017/10/25 NodeJs
微信小程序实现打开内置地图功能【附源码下载】
2017/12/07 Javascript
JS和Canvas实现图片的预览压缩和上传功能
2018/03/30 Javascript
JS实现全屏预览F11功能的示例代码
2018/07/23 Javascript
springboot+vue+对接支付宝接口+二维码扫描支付功能(沙箱环境)
2020/10/15 Javascript
[01:04:48]VGJ.S vs TNC Supermajor 败者组 BO3 第一场 6.6
2018/06/07 DOTA
Python3+django2.0+apache2+ubuntu14部署网站上线的方法
2018/07/07 Python
利用python修改json文件的value方法
2018/12/31 Python
python绘制随机网络图形示例
2019/11/21 Python
python多项式拟合之np.polyfit 和 np.polyld详解
2020/02/18 Python
解决Keras自带数据集与预训练model下载太慢问题
2020/06/12 Python
关于pycharm 切换 python3.9 报错 ‘HTMLParser‘ object has no attribute ‘unescape‘ 的问题
2020/11/24 Python
详解通过变换矩阵实现canvas的缩放功能
2019/01/14 HTML / CSS
澳大利亚最好的厨具店:Kitchen Warehouse
2018/03/13 全球购物
英国太阳镜品牌:Taylor Morris Eyewear
2018/04/18 全球购物
美国家居装饰店:Pier 1
2019/09/04 全球购物
室内设计专业个人的自我评价
2013/10/19 职场文书
领导工作表现评语
2015/01/04 职场文书
2015年五一劳动节慰问信
2015/03/23 职场文书
用Python进行栅格数据的分区统计和批量提取
2021/05/27 Python
Python如何导出导入所有依赖包详解
2021/06/08 Python
Vue过滤器(filter)实现及应用场景详解
2021/06/15 Vue.js
MySQL 服务和数据库管理
2021/11/11 MySQL