专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
码农翻身  ·  中国的大模型怎么突然间就领先了? ·  昨天  
OSC开源社区  ·  升级到Svelte ... ·  5 天前  
程序猿  ·  “未来 3 年内,Python 在 AI ... ·  5 天前  
程序员的那些事  ·  成人玩偶 + ... ·  5 天前  
51好读  ›  专栏  ›  SegmentFault思否

基于 Python 的 Scrapy 爬虫入门:页面提取

SegmentFault思否  · 公众号  · 程序员  · 2017-12-04 08:00

正文

目录

  • 基于 Python 的 Scrapy 爬虫入门:环境搭建

  • 基于 Python 的 Scrapy 爬虫入门:页面提取

  • 基于 Python 的 Scrapy 爬虫入门:图片处理

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

一、内容分析

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

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

  1. class="content">
  2.    

    class="widget-gallery">
  3.        

      class="pagelist-wrapper">

  4.            

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

    1. class="content">
    2.    

      class="widget-gallery">

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

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

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

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

  1. {

  2.  "postList": [

  3.    {

  4.      "post_id": "15624611",

  5.      "type": "multi-photo",

  6.      "url": "https://weishexi.tuchong.com/15624611/",

  7.       "site_id": "443122",

  8.      "author_id": "443122",

  9.      "published_at": "2017-10-28 18:01:03",

  10.      "excerpt": "10月18日",

  11.      "favorites": 4052,

  12.      "comments": 353,

  13.      "rewardable": true,

  14.      "parent_comments": "165",

  15.      "rewards": "2",

  16.      "views": 52709,

  17.       "title": "微风不燥  秋意正好",

  18.      "image_count": 15,

  19.      "images": [

  20.        {

  21.          "img_id": 11585752,

  22.          "user_id": 443122,

  23.          "title": "",

  24.          "excerpt": "",

  25.          "width": 5016,

  26.          "height": 3840

  27.        },

  28.        {

  29.           "img_id": 11585737,

  30.          "user_id": 443122,

  31.          "title": "",

  32.          "excerpt": "",

  33.          "width": 3840,

  34.          "height": 5760

  35.        },

  36.        ...

  37.      ],

  38.      "title_image": null,

  39.      "tags": [

  40.        {

  41.           "tag_id": 131,

  42.          "type": "subject",

  43.          "tag_name": "人像",

  44.          "event_type": "",

  45.          "vote": ""

  46.        },

  47.        {

  48.          "tag_id": 564,

  49.          "type": "subject",

  50.          "tag_name": "美女",

  51.          "event_type": "",

  52.          "vote": ""

  53.        }

  54.      ],

  55.      "favorite_list_prefix": [],

  56.      "reward_list_prefix": [],

  57.      "comment_list_prefix": [],

  58.      "cover_image_src": "https://photo.tuchong.com/443122/g/11585752.webp",

  59.      "is_favorite": false

  60.    }

  61.  ],

  62.  "siteList": {...},

  63.  "following": false,

  64.  "coverUrl": "https://photo.tuchong.com/443122/ft640/11585752.webp",

  65.   "tag_name": "美女",

  66.  "tag_id": "564",

  67.  "url": "https://tuchong.com/tags/%E7%BE%8E%E5%A5%B3/",

  68.  "more": true,

  69.  "result": "SUCCESS"

  70. }

根据属性名称很容易知道对应的内容含义,这里我们只需关心 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 ,很容易通过上面的信息合成。

二、创建项目

  1. 进入cmder命令行工具,输入 workon scrapy 进入之前建立的虚拟环境,此时命令行提示符前会出现 (Scrapy) 标识,标识处于该虚拟环境中,相关的路径都会添加到PATH环境变量中便于开发及使用。

  2. 输入 scrapy startproject tuchong 创建项目 tuchong

  3. 进入项目主目录,输入 scrapy genspider photo tuchong.com 创建一个爬虫名称叫 photo (不能与项目同名),爬取 tuchong.com 域名(这个需要修改,此处先输个大概地址),的一个项目内可以包含多个爬虫

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

  1. (PROJECT)

  2. │  scrapy.cfg

  3. └─tuchong

  4.    │  items.py

  5.    │  middlewares.py

  6.    │  pipelines.py

  7.    │  settings.py

  8.    │  __init__.py

  9.    │

  10.    ├─spiders

  11.    │  │  photo.py

  12.    │  │  __init__.py

  13.    │  │

  14.    │  └─__pycache__

  15.    │          __init__.cpython-36.pyc

  16.    │

  17.    └─__pycache__

  18.            settings.cpython-36.pyc

  19.            __init__.cpython-36.pyc

  • scrapy.cfg :基础设置

  • items.py :抓取条目的结构定义

  • middlewares.py :中间件定义,此例中无需改动

  • pipelines.py :管道定义,用于抓取数据后的处理

  • settings.py :全局设置

  • spiders\photo.py :爬虫主体,定义如何抓取需要的数据

三、主要代码

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

  1. import scrapy

  2. class TuchongItem(scrapy.Item):

  3.    post_id = scrapy.Field()

  4.    site_id = scrapy. Field()

  5.    title = scrapy.Field()

  6.    type = scrapy.Field()

  7.    url = scrapy.Field()

  8.    image_count = scrapy.Field()

  9.    images = scrapy.Field()

  10.    tags = scrapy.Field()

  11.    excerpt = scrapy.Field()

  12.    ...

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

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

  1. import scrapy

  2. class PhotoSpider(scrapy.Spider):

  3.    name = 'photo'

  4.    allowed_domains = ['tuchong.com']

  5.    start_urls = ['http://tuchong.com/']

  6.    def parse(self, response):

  7.        pass

爬虫名 name ,允许的域名 allowed_domains (如果链接不属于此域名将丢弃,允许多个) ,起始地址 start_urls 将从这里定义的地址抓取(允许多个) 函数 parse 是处理请求内容的默认回调函数,参数 response 为请求内容,页面内容文本保存在 response.body 中,我们需要对默认代码稍加修改,让其满足多页面循环发送请求,这需要重载 start_requests 函数,通过循环语句构建多页的链接请求,修改后代码如下:

  1. import scrapy, json

  2. from ..items import TuchongItem

  3. class PhotoSpider(scrapy.Spider):

  4.    name = 'photo'

  5.    # allowed_domains = ['tuchong.com']

  6.     # start_urls = ['http://tuchong.com/']

  7.    def start_requests(self):

  8.        url = 'https://tuchong.com/rest/tags/%s/posts?page=%d&count=20&order=weekly';

  9.        # 抓取10个页面,每页20个图集

  10.        # 指定 parse 作为回调函数并返回 Requests 请求对象

  11.        for page in range(1, 11):

  12.            yield scrapy.Request(url=url % ('美女', page), callback=self.parse)

  13.    # 回调函数,处理抓取内容填充 TuchongItem 属性

  14.    def parse(self, response):

  15.        body = json.loads(response.body_as_unicode())

  16.        items = []

  17.         for post in body['postList']:

  18.            item = TuchongItem()

  19.            item['type'] = post['type']

  20.            item['post_id'] = post['post_id']

  21.            item['site_id'] = post['site_id']

  22.            item[ 'title'] = post['title']

  23.            item['url'] = post['url']

  24.            item['excerpt'] = post['excerpt']

  25.            item['image_count'] = int(post['image_count'])

  26.            item['images'] = {}

  27.            # 将 images 处理成 {img_id: img_url} 对象数组

  28.             for img in post.get('images', ''):

  29.                img_id = img['img_id']

  30.                url = 'https://photo.tuchong.com/%s/f/%s.jpg' % (item['site_id'], img_id)

  31.                item['images'][img_id] = url

  32.            item['tags' ] = []

  33.            # 将 tags 处理成 tag_name 数组

  34.            for tag in post.get('tags', ''):

  35.                item['tags'].append(tag['tag_name'])

  36.            items.append(item)

  37.        return items

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

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

  1. ...

  2.    def process_item(self, item, spider):

  3.        # 不符合条件触发 scrapy.exceptions.DropItem 异常,符合条件的输出地址

  4.        if int(item['image_count']) < 3:

  5.            raise DropItem("美女太少: " + item['url'])

  6.        elif item['type'] != 'multi-photo':

  7.            raise DropItem("格式不对: " + + item['url'])

  8.        else







请到「今天看啥」查看全文