使用pyspider抓取起點中文網小說數據

簡介

pyspider是國人開發的相當好用的爬蟲框架。雖然網上教程不是很多,但是文檔詳細,操作簡單,非常適合用來做爬蟲練習或者實現一些抓取數據的需求。

本文就以抓取起點中文小說網的小說作品基礎信息作為目標,講解如何使用pyspider框架采集數據。

關于為何要選擇起點作為目標,其一、筆者作為網文愛好者,也想收集起點小說作品信息,找些熱門小說看;其二、起點作為比較成熟的小說網站,再反爬蟲方面應該有對應策略,剛好練習一下爬蟲怎么規避這些策略。

在閱讀本文之前,建議先看一下文檔及框架作者本人寫的中文教程
pyspider 爬蟲教程(一):HTML 和 CSS 選擇器
pyspider 爬蟲教程(二):AJAX 和 HTTP
pyspider 爬蟲教程(三):使用 PhantomJS 渲染帶 JS 的頁面

pyspider安裝及啟動

安裝很簡單,如果已安裝pip,直接執行命令

pip install pyspider

由于目前很多網站都是動態js生成頁面,需要安裝PhantomJS來獲得js執行后的頁面,而不是原本靜態的html頁面,我們再來裝一下

pip install  phantomjs

待安裝完成后,我們先看一下pyspider對應的可執行命令

Usage: pyspider [OPTIONS] COMMAND [ARGS]...

  A powerful spider system in python.

Options:
  -c, --config FILENAME    a json file with default values for subcommands.
                           {“webui”: {“port”:5001}}
  --logging-config TEXT    logging config file for built-in python logging
                           module  [default: pyspider/pyspider/logging.conf]
  --debug                  debug mode
  --queue-maxsize INTEGER  maxsize of queue
  --taskdb TEXT            database url for taskdb, default: sqlite
  --projectdb TEXT         database url for projectdb, default: sqlite
  --resultdb TEXT          database url for resultdb, default: sqlite
  --message-queue TEXT     connection url to message queue, default: builtin
                           multiprocessing.Queue
  --amqp-url TEXT          [deprecated] amqp url for rabbitmq. please use
                           --message-queue instead.
  --beanstalk TEXT         [deprecated] beanstalk config for beanstalk queue.
                           please use --message-queue instead.
  --phantomjs-proxy TEXT   phantomjs proxy ip:port
  --data-path TEXT         data dir path
  --version                Show the version and exit.
  --help                   Show this message and exit.

在這里我們直接執行如下命令啟動,更復雜的命令參看文檔

pyspider  --data-path  數據存放路徑  all

爬蟲腳本編寫&執行

首先看一下啟動成功后,瀏覽器訪問127.0.0.1:5000地址的界面如下

點擊Create,新建項目

點擊生成的項目名,進入腳本編寫&調試頁面

Paste_Image.png

先看一下對應的爬蟲腳本

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# Created on 2017-01-13 11:38:33
# Project: qidian_allbook
import time
from pyspider.libs.base_handler import *
import requests
import random
import re


PAGE_START = 1
PAGE_END = 27754

def get_proxy():
    return requests.get("http://127.0.0.1:5001/get/").content

def get_all_proxy():
    return requests.get("http://127.0.0.1:5001/get_all/").content

def delete_proxy(proxy):
    requests.get("http://127.0.0.1:5001/delete/?proxy={}".format(proxy))

class Handler(BaseHandler):
    headers= {
        #"Host": "book.qidian.com",
        #"Connection": "keep-alive",
        #"Accept-Encoding": "gzip, deflate, sdch",
        #"Accept-Language":"zh-CN,zh;q=0.8,en;q=0.6",
        #"Referer":"https://www.baidu.com",      "Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        #"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36"
    }

    crawl_config = {
        "headers" : headers,
        "timeout" : 1000
    }
    
    def __init__(self):
        self.baseUrl = "http://a.qidian.com/?size=-1&sign=-1&tag=-1&chanId=-1&subCateId=-1&orderId=&update=-1&month=-1&style=1&action=-1&vip=-1&page="
        self.page_num = PAGE_START
        self.total_num = PAGE_END
        #self.proxy_all = get_all_proxy()
        #pattern = re.compile(r'"(.+?)"',re.S)
        #self.proxy = pattern.findall(self.proxy_all)
        #self.index = 1
        

    @every(minutes=24 * 60)
    @config(priority=3)
    def on_start(self):
        while self.page_num <= self.total_num:
            url = self.baseUrl + str(self.page_num)
            self.crawl(url, callback=self.index_page)
            self.page_num += 1


    
    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
        for each in response.doc('h4 > a').items():
            self.crawl(each.attr.href, callback=self.detail_page)


    def detail_page(self, response):
        url = response.url
        name = response.doc('h1 > em').text()
        author = response.doc('h1 a').text()
        
        word_count = response.doc('.book-info > p > em').eq(0).text()
        click_count = response.doc('.book-info > p > em').eq(1).text()
        recommend_count = response.doc('.book-info > p > em').eq(2).text()
            
        return {
            "url": url,
            "name": name,
            "author": author,
            "word_count": word_count,
            "click_count": click_count,
            "recommend_count": recommend_count
        }

腳本編寫及測試抓取遇到的問題

1.測試抓取時,運行一段時間后出現所有抓取鏈接均FetchError的報錯,抓取失敗
失敗原因:未設置User-Agent 及 抓取速率太快,導致IP被封禁

解決辦法:
1) 設置User-Agent,調整速率從1->0.7
2) 使用代理IP,防止被封禁,這里筆者嘗試使用搭建簡易免費代理IP池,但是由于免費代理大多不可用,會導致抓取不穩定,還是決定放棄使用

2.筆者本來是打算通過不斷抓取下一頁的鏈接,來遍歷所有小說作品的,可是由于這部分是JS動態生成的,雖然使用phantomjs,能解決這個問題(具體見作者教程3),但是使用phantomjs會導致抓取效率變低,后來還是選擇采用固定首尾頁數(PAGE_START,PAGE_END)的方法

self.crawl(url, callback=self.index_page,fetch_type='js')

3.當使用css選擇器有多個數據時,怎么獲取自己想要的
比如在小說詳細頁,有字數,點擊數,推薦數三個
其css selector均為 .book-info > p > em,要獲取對應的次數只能使用pyquery的.eq(index)的方法去獲取對應的文本數據了

word_count = response.doc('.book-info > p > em').eq(0).text()
click_count = response.doc('.book-info > p > em').eq(1).text()
recommend_count = response.doc('.book-info > p > em').eq(2).text()

4.如果遇到抓取的鏈接是https,而不是http的,使用self.crawl()方法時,需要加入參數validate_cert =False,同時需要確保pyspider --version 版本再0.3.6.0之上
具體解決方法,可以查看如下鏈接:
PySpider HTTP 599: SSL certificate problem錯誤的解決方法

執行結果及簡單數據分析

共采集到328615部小說的作品信息

點擊榜前十:
0: 斗破蒼穹 點擊次數為:150279300
1: 盤龍 點擊次數為:94564599
2: 吞噬星空 點擊次數為:84666200
3: 從零開始 點擊次數為:64794200
4: 斗羅大陸 點擊次數為:63158100
5: 遮天 點擊次數為:62921100
6: 莽荒紀 點擊次數為:61701100
7: 神墓 點擊次數為:52844700
8: 星辰變 點擊次數為:49905900
9: 陽神 點擊次數為:49869399

推薦榜前十:
0: 從零開始 推薦次數為:13328400
1: 盤龍 推薦次數為:7716700
2: 吞噬星空 推薦次數為:7463500
3: 江山美人志 推薦次數為:7307300
4: 一念永恒 推薦次數為:6909400
5: 大主宰 推薦次數為:6804100
6: 星辰變 推薦次數為:6714600
7: 我真是大明星 推薦次數為:6689900
8: 斗破蒼穹 推薦次數為:6644200
9: 完美世界 推薦次數為:6625200

字數榜前十:
0: 帶著農場混異界 字數為:22389600
1: 重生之妖孽人生 字數為:21814900
2: 從零開始 字數為:20180800
3: 暗黑破壞神之毀滅 字數為:15993400
4: 修神外傳 字數為:15944200
5: 煮酒點江山 字數為:15167600
6: 重生1991 字數為:13475700
7: 三國小兵之霸途 字數為:13108200
8: 校花的貼身高手 字數為:12295000
9: 重生之資源大亨 字數為:12141100

簡單數據分析之二
采用SCWS 中文分詞對所有作品名字進行分詞統計,得到出現頻率最高的排行

看起來如果寫小說,起個『重生之我的神魔異世界』這類標題是不是吊炸天

詞出現頻率排行:
之  出現次數為:54104
的  出現次數為:28373
神  出現次數為:12308
異  出現次數為:12051
天  出現次數為:11507
界  出現次數為:11280
我  出現次數為:10813
魔  出現次數為:10008
仙  出現次數為:8543
世  出現次數為:6408
重生  出現次數為:6225
劍  出現次數為:6202
網游  出現次數為:5533
世界  出現次數為:5472
修  出現次數為:5372
錄  出現次數為:4927
道  出現次數為:4658
戰  出現次數為:4632
魂  出現次數為:4332
天下  出現次數為:4129

簡單數據分析之三
簡單統計一下起點作者的作品數排序
武俠精品應該是起點的官方作者號吧,不然194本作品也太恐怖了
也發現了不少熟悉的大神,比如唐家三少,流浪的蛤蟆,骷髏精靈等,有些作品還是可以看看的

0: 作者:武俠精品 發布作品數:194
1: 作者:石三 發布作品數:15
2: 作者:愛妻族 發布作品數:13
3: 作者:唐家三少 發布作品數:12
4: 作者:流浪的蛤蟆 發布作品數:12
5: 作者:莊小街 發布作品數:11
6: 作者:殷揚 發布作品數:11
7: 作者:沐軼 發布作品數:11
8: 作者:飄零幻 發布作品數:10
9: 作者:天凈沙秋思 發布作品數:10
10: 作者:骷髏精靈 發布作品數:10

未完待續

  1. 由于要規避反爬蟲策略,導致目前抓取效率偏低,目前一天只能抓取9W條數據,有沒有方法優化?

  2. 目前抓取的數據保存在pyspider自帶的result.db (sqlite3數據庫)中,導致數據分析不太方便,可否有別的存儲方式?mongodb?mysql?

  3. 抓取時設定的是總共有27754頁,那按理來說總共應該有20*27754=55w數據才對,可是目前最后抓取出來總共只有328615的作品數據,哪里少了?是作品詳情鏈接無效了?還是抓取失敗了?

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容