online gambling singapore online gambling singapore online slot malaysia online slot malaysia mega888 malaysia slot gacor live casino malaysia online betting malaysia mega888 mega888 mega888 mega888 mega888 mega888 mega888 mega888 mega888 Python 的Scrapy 爬蟲入門:程式碼詳解

摘要: 創建一個爬蟲項目,以圖蟲網為例抓取裡面的圖片。在頂部菜單“發現” “標籤”裡面是對各種圖片的分類,點擊一個標籤,我們以此作為爬蟲入口,分析一下該頁面


圖名

 

一、內容分析

接下來創建一個爬蟲項目,以圖蟲網為例抓取裡面的圖片。在頂部菜單“發現” “標籤”裡面是對各種圖片的分類,點擊一個標籤,比如“美女”,網頁的鏈接為: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 ,很容易通過上面的信息合成。

二、創建項目

  1. 進入cmder命令行工具,輸入workon scrapy 進入之前建立的虛擬環境,此時命令行提示符前會出現(Scrapy) 標識,標識處於該虛擬環境中,相關的路徑都會添加到PATH環境變量中便於開發及使用。
  2. 輸入 scrapy startproject tuchong 創建項目tuchong
  3. 進入項目主目錄,輸入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'] 進行判斷,如果存在則跳過。

作者:大蟲

轉貼自: 36 大數據


留下你的回應

以訪客張貼回應

0
  • 找不到回應