1. 程式人生 > >Python3網路爬蟲:Scrapy入門實戰之爬取動態網頁圖片

Python3網路爬蟲:Scrapy入門實戰之爬取動態網頁圖片

Python版本: python3.+
執行環境: Mac OS
IDE: pycharm

一 、前言

上一篇部落格寫了一下自己理解的Scrapy的框架,其中有包括Scrapy的工作流程和安裝方法。學有所用,這次就進行一個實戰來磨練磨練,從理論到實踐。

二 、Scrapy相關方法介紹

1、 搭建Scrapy專案

在成功安裝Scrapy的前提下,在終端進入你想要搭建Scrapy專案的路徑下。
比如我想在/Users/hyl/Documents/scrapy/下搭建一個名為unsplash的工程:
1. 在終端輸入 cd /Users/hyl/Documents/scrapy
2. 再輸入scrapy startproject unsplash

scrapy startproject是固定命令,後面的unsplash是自己想起的工程名字。
該命令將會建立包含下列內容的scrapy目錄:

unsplash/
  |- scrapy.cfg
  |- unsplash/
       |- __init__.py
       |- items.py
       |- middlewares.py
       |- pipelines.py
       |- settings.py
       |- spiders/
            |- __init__.py
            |- ...

這些檔案分別是:

  • scrapy.cfg: 專案的配置檔案;
  • unsplash/: 該專案的python模組。之後將在此加入Spider程式碼;
  • unsplash/items.py: 專案中的item檔案;
  • unsplash/middlewares .py:專案中的中介軟體;
  • unsplash/pipelines.py: 專案中的pipelines檔案;
  • unsplash/settings.py: 專案的設定檔案;
  • unsplash/spiders/: 放置spider程式碼的目錄。

2、 shell分析

在編寫程式之前,我們可以使用Scrapy內建的Scrapy shell,分析下目標網頁,為後編寫梳理思路。
Scrapy shell是一個互動終端,我們能夠在不啟動spider的情況下嘗試及除錯我們的爬取程式碼。
該終端是用來測試 XPathCSS表示式,檢視他們的工作方式及從爬取的網頁中提取的資料。 在編spider的時候,該終端提供了互動性測試我們的表示式程式碼的功能,免去了每次修改後執行spider的麻煩。
概括一下,就是我們現在shell上把XPath和CSS表示式的查詢程式碼測試成功,為了之後在寫Spider能夠避免每次執行後再修改這樣繁瑣的操作。
https://unsplash.com/為例:
1. 在終端鍵入命令

scrapy shell 'https://unsplash.com/' --nolog
--nolog 的作用是不顯示日誌資訊

這裡寫圖片描述
其中
2. Scrapy shell中,我們可以通過如下指令列印網頁的body資訊:

response.body

在終端裡現實的內容,是未經過排版的,因此,想要通過response.body來查詢元素,是非常困難的。所以,可以在網頁上,通過審查元素的方式進行分析:
這裡寫圖片描述
再通過response.xpath()來驗證我們xpath查詢語句是否正確
xpath的語法,可以在http://www.w3school.com.cn/xpath/xpath_syntax.asp瞭解

response.xpath('//a[@title="View the photo By Meduana"]')

但是因為該網站是動態載入的,所以我們不能夠通過這種方式來獲取我們想要的資訊。這裡寫圖片描述

三 、網頁分析

此次要爬取的網站url:https://unsplash.com/

對該網站的分析,已在之前的一篇部落格Python3網路爬蟲:requests爬取動態網頁內容有描述,在此,僅做一個總結:

  • 應對該網站的反爬蟲機制: 在headers中加上authorization欄位,該欄位的值,需要通過抓包來獲取。
  • json源地址: http://unsplash.com/napi/feeds/home
  • 圖片下載地址:
    https://unsplash.com/photos/*****/download?force=true
    其中*為圖片id

四 、Scrapy程式編寫

1、 spider測試

在unsplash/unsplash目錄下建立unsplash_spider.py檔案,編寫程式碼如下:

#-*- coding:utf-8 -*-
import scrapy,json

class UnsplashSpider(scrapy.Spider):

    name = 'unsplash'

    start_urls = ['http://unsplash.com/napi/feeds/home']


    def parse(self, response):
        try:
            dic = json.loads(response.text)
            photos = dic['photos']
            print("next_page: %s" % dic['next_page'])
            for photo in photos :
                print("id: %s " % photo['id'])
        except:
            print('error')
  • name:自己定義的內容,在執行工程的時候需要用到的標識;
  • start_urls:開始爬取的url,需要注意的是這個url連結需要放在列表裡;
  • def parse(self, response) :請求分析的回撥函式,如果不定義start_requests(self),獲得的請求直接從這個函式分析;

然後在終端鍵入 Scrapy shell命令

scrapy crawl comic --nolog

但是會發現不顯示任何值,就連error的錯誤列印都沒有。
這是因為scrapy在呼叫預設的start_requests發方法時,就已經報錯了。其原因是傳送的headers 中 沒有authorization的欄位,導致的爬取失敗,沒有收到response。
這時,我們為了避免每次都單獨設定headers,在setting中設定

DEFAULT_REQUEST_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
                          'AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/61.0.3163.79 Safari/537.36',
    'authorization': '********************'
}

再重新訪問,這樣,就能得到我們想要的資料了。
這裡寫圖片描述

2、 item編寫

我們將上面獲得的id和next_page存入自己編寫的item類中,以便於 通過item pipeline來下載圖片

import scrapy
class UnsplashItem(scrapy.Item):
    image_ids = scrapy.Field()
    image_urls = scrapy.Field()
    image_paths = scrapy.Field()
  • image_ids:圖片的id
  • image_urls:圖片的下載地址
  • image_paths:圖片的本地儲存路徑

3、 Pipelines編寫

當在程式返回一個item的時候,scrapy會自動呼叫pipeline,所以我們可以把下載圖片的功能放在pipeline中去

# -*- coding: utf-8 -*-

# Define your item pipelines here
#
# Don't forget to add your pipeline to the ITEM_PIPELINES setting
# See: http://doc.scrapy.org/en/latest/topics/item-pipeline.html

from unsplash import settings
import os
import requests
class UnsplashPipeline(object):
    def process_item(self, item, spider):
        if 'image_urls' in item:

            if not os.path.exists(settings.IMAGES_STORE):
                os.makedirs(settings.IMAGES_STORE)
            for index in range(len(item['image_urls'])):
                image_url = item['image_urls'][index]
                image_id = item['image_ids'][index]
                file_name = image_id+'.jpg'
                file_path = settings.IMAGES_STORE+'/'+file_name
                # 儲存圖片
                with open(file_path,'wb') as f:
                    response = requests.get(image_url,headers = settings.DEFAULT_REQUEST_HEADERS,verify=False)
                    for chunk in response.iter_content(1024):
                        if not chunk:
                            break
                        f.write(chunk)
                item['image_paths'] = [file_path]


        return item

其中使用到了settings的IMAGES_STORE引數。對settings,可以理解為存放scrapy engine全域性變數的檔案。
所以在使用IMAGES_STORE之前,要在settings中加上

 IMAGES_STORE = './images'

而想要scrapy engine 能夠辨別我們寫的UnsplashPipeline類,同樣要在setting中加上

ITEM_PIPELINES = {
   'unsplash.pipelines.UnsplashPipeline': 1,
}

4、 spider完整程式碼實現

我們要在spider做的事,就是分析通過request請求獲取的response,來獲得我們想要的資料,儲存到我們定義好的item中。
在scrapy engine的支援下,我們返回的item,會自動去呼叫pipelines;而返回的request請求,若不指定回撥函式,則會自動去呼叫預設的parse方法。

#-*- coding:utf-8 -*-
import scrapy,json
from unsplash.items import UnsplashItem
class UnsplashSpider(scrapy.Spider):

    name = 'unsplash'

    start_urls = ['http://unsplash.com/napi/feeds/home']

    # def start_requests(self):
    #     yield scrapy.Request(self.start_urls[0],callback=self.parse)
    def __init__(self):
        self.download_format = 'https://unsplash.com/photos/{}/download?force=true'

    def parse(self, response):
        # try:
        dic = json.loads(response.text)
        photos = dic['photos']
        next_page = dic['next_page']
        # print("next_page: %s" % dic['next_page'])
        for photo in photos :
            image_id = photo['id']
            item = UnsplashItem()
            item['image_ids'] = [image_id]
            image_url = self.download_format.format(image_id)
            item['image_urls'] = [image_url]
            yield item
        yield scrapy.Request(url=next_page,callback=self.parse)
        # except:
        #     print('error')

關於yield起到的作用,例如for迴圈下的yield item,可以理解成:類似於return的作用,該函式會返回一個item,但是並不會中斷該方法,而是會繼續執行下面的程式碼。更詳細的解釋。可以參閱
Python yield 使用淺析

5、 settings 彙總

# -*- coding: utf-8 -*-

# Scrapy settings for unsplash project
#
# For simplicity, this file contains only settings considered important or
# commonly used. You can find more settings consulting the documentation:
#
#     http://doc.scrapy.org/en/latest/topics/settings.html
#     http://scrapy.readthedocs.org/en/latest/topics/downloader-middleware.html
#     http://scrapy.readthedocs.org/en/latest/topics/spider-middleware.html

BOT_NAME = 'unsplash'

SPIDER_MODULES = ['unsplash.spiders']
NEWSPIDER_MODULE = 'unsplash.spiders'
IMAGES_STORE = './images'
ITEM_PIPELINES = {
   'unsplash.pipelines.UnsplashPipeline': 1,
}

DEFAULT_REQUEST_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) '
                          'AppleWebKit/537.36 (KHTML, like Gecko) '
                          'Chrome/61.0.3163.79 Safari/537.36',
    'authorization': '*********',
}
ROBOTSTXT_OBEY = False
DOWNLOAD_DELAY = 0.25    # 250 ms of delay
  • BOT_NAME:自動生成的內容,根名字;
  • SPIDER_MODULES:自動生成的內容;
  • NEWSPIDER_MODULE:自動生成的內容;
  • ROBOTSTXT_OBEY:自動生成的內容,是否遵守robots.txt規則,這裡選擇不遵守;
  • ITEM_PIPELINES:定義item的pipeline;
  • IMAGES_STORE:圖片儲存的根路徑;
  • DOWNLOAD_DELAY:下載延時,這裡使用250ms延時

完成上述程式碼的編寫,再在終端下執行命令

scrapy crawl unsplash

這裡可以不關閉日誌,來判斷爬蟲執行是否正常。
讓爬蟲跑一段時間吧。之後,那些精美的圖片就已經儲存到你的電腦上了 ^_^
這裡寫圖片描述

五 小結

本次例項只是為了來加深對scrapy的理解。其中對圖片的下載,使用的是requests庫的方法。並不是scrapy自帶的ImagesPipeline方法。關於對ImagesPipeline的使用,應該會在下篇部落格介紹吧。
這個demo我也上傳到Github上了。URL:https://github.com/hylusst/unsplash_spider-scrapy-

本文僅就我對scrapy的理解進行實踐,如有錯誤,望指正 。