NO IMAGE

?wx_fmt=gif&wxfrom=5&wx_lazy=1

本文來自作者 孫亖 在 GitChat 上分享 「如何用 Python 爬取網頁製作電子書」,「閱讀原文」檢視交流實錄。

「文末高能」

編輯 | 哈比

0 前言

有人爬取資料分析黃金週旅遊景點,有人爬取資料分析相親,有人大資料分析雙十一,連小學生寫論文都用上了大資料。

我們每個人每天都在往網上通過微信、微博、淘寶等上傳我們的個人資訊,現在就連我們的錢都是放在網上,以後到強人工智慧,我們連決策都要依靠網路。網上的資料就是資源和寶藏,我們需要一把鏟子來挖掘它。

最近,AI 的興起讓 Python 火了一把。實際上 Python 擁有龐大的第三方支援,生態系統非常完整,可以適用各種場景和行業。

這次,我們準備通過 Python 學習爬蟲的開發,既簡單有趣,而且是資料採集重要一環。同時脫離應用談技術就是耍流氓,通過製作電子書學習資料的收集與整理,即能學到東西又有實用價值。

我們將通過爬取網頁資訊這個很小的應用場景來體會資料預處理的思想,並從中學習瞭解資料處理中抓取、處理、分組、儲存等過程的實現。

我這次分享主要分為以下幾個部分:

Python 語法:通過分享掌握簡單的 Python 開發語法和思路,側重於後面爬蟲開發的需要用的內容;Scrapy 爬蟲開發:通過分享瞭解基本的 Scrapy 開發,並實現從網路爬取資料,使用 Sigil 製作 epub 電子書。

最後,我希望通過分享,讓更多人能夠入門並喜歡上 Python 開發,掌握 Scrapy 爬蟲開發的思路和方法。

1 Python 開發

1.1 Windows 下環境安裝

熟悉 Windows 的安裝 Python 不難,首先官網下載:https://www.python.org/downloads/。

有兩個版本,根據需要選擇自己的版本,現在越來越多的庫開始支援 3,所以建議下載 3,這裡我們以 2 為例。

雙擊下載的安裝檔案,一路 Next 即可,但是要注意勾選 __pip__ 和 Add python.exe to Path

?wx_fmt=png

pip 是 Python 生態體系裡面的包管理工具,很多第三方庫可以通過它方便的管理。

安裝 Finish 之後,開啟命令列視窗,輸入 Python:

?wx_fmt=png

如果出現這個介面說明安裝成功了,如果出現下面的情況:

‘python’ 不是內部或外部命令,也不是可執行的程式或批處理檔案。

需要把 python.exe 的目錄新增到 path 中,一般是 C:/Python27。

1.2 Python 之 HelloWorld

目前我所接觸過的所有程式語言都只要掌握三個內容就可以了:就是輸入、處理、輸出。我們已經安裝好了 Python,可以來一個最俗套的程式。

首先我們開啟 windows 的控制檯,然後輸入 python 回車,然後輸入如下程式碼:

print ‘Hello world!’

我就問你俗不俗,好了,看結果:

?wx_fmt=png

根據我上面的說法,這個程式的輸入就是 Hello World 字串,處理使系統內部的輸出處理,輸出結果就是 ‘Hello World’。

我們還可以把程式碼寫在檔案裡面:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

print ‘Hello World!’

執行效果:

?wx_fmt=png

我們說這個不是單純的秀一下,以前沒有使用者介面的時候 print 可以作為人機互動用途,現在多數是用於除錯,可以在程式執行的時候快速的輸出程式結果或者過程結果。

1.3 做菜與程式設計

現在有個很有意思的說法:生資料(原始資料)就是沒有處理過的資料,熟資料(Cooked Data)是指原始資料經過加工處理後的資料,處理包括解壓縮、組織,或者是分析和提出,以備將來使用。

這就像做菜生菜是輸入,菜譜是程式,洗、切、烹飪等處理是程式執行過程,最後輸出的熟菜。但不管生菜、熟菜都是菜,或者都是物質。

準備食材

在程式世界裡的物質組成就是資料,就像有蘿蔔白菜等不同的品種一樣,資料也有不同的型別。我目前所接觸到的資料型別主要有以下幾種:

  • 物理類:資料在實體記憶體中的表達儲存方式;

    • 位元組

  • 資料類:資料類中的具體型別代表了不同精度和記憶體中不同的儲存結構;

    • 整數

    • 浮點數

    • 長整型

    • 雙精度

  • 字元類:就是文字字元相關的資料型別;

    • 字元

    • 字串

  • 邏輯類:就是邏輯真與邏輯假;

    • 布林值

  • 複合類:由各基本的資料型別按照一定的結構組合而成的資料;

    • 結構體

    • 集合

    • 字典

    • 列表

    • 序列

    • Hash 表

    • ……

這裡我強調幾點:

  • 首先,這個分類不是某種語言特有,目前大多數程式語言都差不多,你理解這個思想就把自己的程式設計能力擴充套件了。

  • 其次,它不需要專門記憶,程式設計是程式性的知識,運用的知識,是一種技能,你要做什麼菜,你來這個分類查查需要什麼原材料,再去具體研究,慢慢就會了,不做你記住了也沒用。

  • 用多深,研究多深,不用就別研究浪費時間。比如說,我們一般性應用不會去考慮資料的記憶體模型,但是涉及到精度、效能或者邊界值時我們就需要小心,研究得深一些。

器皿

食材已準備好了,可以下鍋,可鍋在哪裡,你不能放在手裡加工。程式裡我們用變數、常量來盛各種資料,還有個作用域的問題,嚴格的廚房紅案和白案是分開的,有時候砧板是不能互用的。

  • 空值:四大皆空,什麼也不是,不是 0,不是長度為 0 的字串,不是 false,什麼都不是;

  • 變數:學過數學的人都應該有這個概念,反正差不多;

  • 常量:固定不變的量,比如說 π。

烹飪手法

剛查了下,我大天朝常用的烹飪手法多達 20 多種,我歸納了一下,程式設計大概就那麼幾種:

  • 數值計算——加減乘除、位移等;

  • 邏輯計算——邏輯真假判斷;

  • 過程計算——迴圈、巢狀、遞迴等;

  • 資料處理——字串、物件的操作。

菜譜與炒菜

菜都準備好了,下鍋怎麼炒,全靠菜譜,它就是程式,而我們按照菜譜炒菜這個過程就是程式的執行。

Python 或任何一種程式語言都是博大精深,同時又是一種技能,不可能在使用之前完全掌握,也沒必要。

我們需要知道的是我們想吃什麼(程式要輸出什麼),然後再去菜市場買時才找菜譜(搜尋引擎查資料),最後按照我們的需求加工(程式設計)。

1.4 Python 簡單實踐

首先我們來寫三個 Python 檔案:

hello.py
——事情的處理有落點,程式執行有入口,例如:main,這個檔案可以看作程式的入口。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pkg
print ‘Hello World!’
pkg.test()
p = pkg.Person(“Mike”, 23)
p.showInfo()

pkg.py
——程式可以分塊編寫,這樣層次更分明,易於理解和維護,我們在 pkg.py 中編寫一部分功能,作為演示模組。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

def test():
   print “Here is pkg’s test”

class Person(object):
   def __init__(self, name, age):
       self.name = name
       self.age = age
       pass

   def showInfo(self):
       print self.name
       print self.age

init.py
——這是一個空檔案,也可以寫程式碼,表明當前路徑是包。

接下來,我們來執行一下:

python hello.py

顯示結果如下:

Hello World!

Here is pkg’s test

Mike

23

我們執行了 hello.py 檔案,然後 hello.py 匯入了包 pkg;包 pkg 定義了一個方法和一個類,我們在 hello.py 檔案裡面呼叫了外部的方法和類。

2 使用 Scrapy 抓取電子書

2.1 寫在爬取資料之前

雖然我們這裡的資料都是從公開的網路獲取,但也不能確定其版權問題,因此獲取的資料僅用於程式設計練習,嚴禁分享或用於其他用途。

好了,現在我們找一個線上看書的網站,找一本書把它下載到本地。首先,我們準備下載工具,就是 Python 的爬蟲框架 Scrapy。

2.2 Scrapy 安裝

安裝完 Python 後可以用以下的命令按照 Scrapy,有些版本的 Python 沒有帶 pip 需要手動安裝。

pip install scrapy

pip 是 Python 的包管理器,大量的第三方包或者說功能可以通過這個工具來管理,所謂包就是模組化的功能集合,基本的技術參考實踐裡面的包。

我安裝成功顯示如下資訊:

> Collecting scrapy
 Downloading Scrapy-1.5.0-py2.py3-none-any.whl (251kB)
   100% |████████████████████████████████| 256kB 181kB/s
Collecting service-identity (from scrapy)
 Downloading service_identity-17.0.0-py2.py3-none-any.whl
Collecting parsel>=1.1 (from scrapy)
 Downloading parsel-1.3.1-py2.py3-none-any.whl
Collecting six>=1.5.2 (from scrapy)
 Downloading six-1.11.0-py2.py3-none-any.whl
Collecting w3lib>=1.17.0 (from scrapy)
 Downloading w3lib-1.18.0-py2.py3-none-any.whl
Collecting lxml (from scrapy)
 Downloading lxml-4.1.1-cp27-cp27m-win_amd64.whl (3.6MB)
   100% |████████████████████████████████| 3.6MB 142kB/s
Collecting Twisted>=13.1.0 (from scrapy)
 Downloading Twisted-17.9.0-cp27-cp27m-win_amd64.whl (3.2MB)
   100% |████████████████████████████████| 3.2MB 169kB/s
Collecting pyOpenSSL (from scrapy)
 Downloading pyOpenSSL-17.5.0-py2.py3-none-any.whl (53kB)
   100% |████████████████████████████████| 61kB 313kB/s
Collecting PyDispatcher>=2.0.5 (from scrapy)
 Downloading PyDispatcher-2.0.5.tar.gz
Collecting queuelib (from scrapy)
 Downloading queuelib-1.4.2-py2.py3-none-any.whl
Collecting cssselect>=0.9 (from scrapy)
 Downloading cssselect-1.0.3-py2.py3-none-any.whl
Collecting pyasn1 (from service-identity->scrapy)
 Downloading pyasn1-0.4.2-py2.py3-none-any.whl (71kB)
   100% |████████████████████████████████| 71kB 328kB/s
Collecting attrs (from service-identity->scrapy)
 Downloading attrs-17.4.0-py2.py3-none-any.whl
Collecting pyasn1-modules (from service-identity->scrapy)
 Downloading pyasn1_modules-0.2.1-py2.py3-none-any.whl (60kB)
   100% |████████████████████████████████| 61kB 347kB/s
Collecting hyperlink>=17.1.1 (from Twisted>=13.1.0->scrapy)
 Downloading hyperlink-17.3.1-py2.py3-none-any.whl (73kB)
   100% |████████████████████████████████| 81kB 407kB/s
Collecting Automat>=0.3.0 (from Twisted>=13.1.0->scrapy)
 Downloading Automat-0.6.0-py2.py3-none-any.whl
Collecting constantly>=15.1 (from Twisted>=13.1.0->scrapy)
 Downloading constantly-15.1.0-py2.py3-none-any.whl
Collecting zope.interface>=3.6.0 (from Twisted>=13.1.0->scrapy)
 Downloading zope.interface-4.4.3-cp27-cp27m-win_amd64.whl (137kB)
   100% |████████████████████████████████| 143kB 279kB/s
Collecting incremental>=16.10.1 (from Twisted>=13.1.0->scrapy)
 Downloading incremental-17.5.0-py2.py3-none-any.whl
Collecting cryptography>=2.1.4 (from pyOpenSSL->scrapy)
 Downloading cryptography-2.1.4-cp27-cp27m-win_amd64.whl (1.3MB)
   100% |████████████████████████████████| 1.3MB 220kB/s
Requirement already satisfied: setuptools in c:\python27\lib\site-packages (from zope.interface>=3.6.0->Twisted>=13.1.0->scrapy)
Collecting idna>=2.1 (from cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading idna-2.6-py2.py3-none-any.whl (56kB)
   100% |████████████████████████████████| 61kB 311kB/s
Collecting cffi>=1.7; platform_python_implementation != “PyPy” (from cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading cffi-1.11.2-cp27-cp27m-win_amd64.whl (163kB)
   100% |████████████████████████████████| 163kB 183kB/s
Collecting enum34; python_version < “3” (from cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading enum34-1.1.6-py2-none-any.whl
Collecting asn1crypto>=0.21.0 (from cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading asn1crypto-0.24.0-py2.py3-none-any.whl (101kB)
   100% |████████████████████████████████| 102kB 194kB/s
Collecting ipaddress; python_version < “3” (from cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading ipaddress-1.0.19.tar.gz
Collecting pycparser (from cffi>=1.7; platform_python_implementation != “PyPy”->cryptography>=2.1.4->pyOpenSSL->scrapy)
 Downloading pycparser-2.18.tar.gz (245kB)
   100% |████████████████████████████████| 256kB 264kB/s
Installing collected packages: pyasn1, six, idna, pycparser, cffi, enum34, asn1crypto, ipaddress, cryptography, pyOpenSSL, attrs, pyasn1-modules, service-identity, w3lib, cssselect, lxml, parsel, hyperlink, Automat, constantly, zope.interface, incremental, Twisted, PyDispatcher, queuelib, scrapy
 Running setup.py install for pycparser … done
 Running setup.py install for ipaddress … done
 Running setup.py install for PyDispatcher … done
Successfully installed Automat-0.6.0 PyDispatcher-2.0.5 Twisted-17.9.0 asn1crypto-0.24.0 attrs-17.4.0 cffi-1.11.2 constantly-15.1.0 cryptography-2.1.4 cssselect-1.0.3 enum34-1.1.6 hyperlink-17.3.1 idna-2.6 incremental-17.5.0 ipaddress-1.0.19 lxml-4.1.1 parsel-1.3.1 pyOpenSSL-17.5.0 pyasn1-0.4.2 pyasn1-modules-0.2.1 pycparser-2.18 queuelib-1.4.2 scrapy-1.5.0 service-identity-17.0.0 six-1.11.0 w3lib-1.18.0 zope.interface-4.4.3

2.3 新建 Scrapy 爬蟲專案

Scrapy 是 Python 程式,同時也是一套框架,提供了一系列工具來簡化開發,因此我們按照 Scrapy 的模式來開發,先新建一個 Scrapy 專案,如下:

scrapy startproject ebook

Scrapy 專案包含一些基礎框架程式碼,我們在此基礎上開發,目錄結構類似下圖:

?wx_fmt=png

2.4 新建 Scrapy 爬蟲

這時,Scrapy 還不知道我們要爬取什麼資料,所以我們要用 Scrapy 工具新建一個爬蟲,命令如下:

scrapy genspider example example.com

下面實操,我們在起點中文網找一篇免費小說的完本,這裡選擇是《修真小主播》。

我們就在前面建立的 Scrapy 專案 ebook 下新建一個爬蟲,命令如下:

cd ebook
scrapy genspider xzxzb qidian.com

執行成功之後,在專案的 spider 目錄下就多了一個 xzxzb.py 的檔案。

2.5 爬蟲思路

怎麼抓取資料,首先我們要看從哪裡取,開啟《修真小主播》的頁面,如下:

?wx_fmt=png

有個目錄頁籤,點選這個頁籤可以看見目錄,使用瀏覽器的元素檢視工具,我們可以定位到目錄和每一章節的相關資訊,根據這些資訊我們就可以爬取到具體的頁面:

?wx_fmt=png

2.6 獲取章節地址

現在我們開啟 xzxzb.py 檔案,就是我們剛剛建立的爬蟲:

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

class XzxzbSpider(scrapy.Spider):
   name = ‘xzxzb’
   allowed_domains = [‘qidian.com’]
   start_urls = [‘http://qidian.com/’]

   def parse(self, response):
       pass

start_urls 就是目錄地址,爬蟲會自動爬這個地址,然後結果就在下面的 parse 中處理。現在我們就來編寫程式碼處理目錄資料,首先爬取小說的主頁,獲取目錄列表:

   def parse(self, response):
       pages = response.xpath(‘//div[@id=”j-catalogWrap”]//ul[@class=”cf”]/li’)
       for page in pages:
           url = page.xpath(‘./child::a/attribute::href’).extract()
           print url
       pass

獲取網頁中的 DOM 資料有兩種方式,一種是使用 CSS 選擇子,另外一種是使用 XML 的 xPath 查詢。

這裡我們用 xPath,相關知識請自行學習,看以上程式碼,首先我們通過 ID 獲取目錄框,獲取類 cf 獲取目錄列表:

pages = response.xpath(‘//div[@id=”j-catalogWrap”]//ul[@class=”cf”]/li’)

接著,遍歷子節點,並查詢 li 標籤內 a 子節點的 href 屬性,最後列印出來:

for page in pages:
           url = page.xpath(‘./child::a/attribute::href’).extract()
           print url

這樣,可以說爬取章節路徑的小爬蟲就寫好了,使用如下命令執行 xzxzb 爬蟲檢視結果:

scrapy crawl xzxzb

這個時候我們的程式可能會出現如下錯誤:


ImportError: No module named win32api

執行下面的語句即可:

pip install pypiwin32

螢幕輸出如下:

> …
> [u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/wrrduN6auIlOBDFlr9quQA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/Jh-J5usgyW62uJcMpdsVgA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/5YXHdBvg1ImaGfXRMrUjdw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/fw5EBeKat-76ItTi_ILQ7A2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/KsFh5VutI6PwrjbX3WA1AA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/-mpKJ01gPp1p4rPq4Fd4KQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/MlZSeYOQxSPM5j8_3RRvhw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/5TXZqGvLi-3M5j8_3RRvhw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/sysD-JPiugv4p8iEw–PPw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/xGckZ01j64-aGfXRMrUjdw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/72lHOJcgmedOBDFlr9quQA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/cZkHZEYnPl22uJcMpdsVgA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/vkNh45O3JsRMs5iq0oQwLQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/ge4m8RjJyPH6ItTi_ILQ7A2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/Y33PuxrKT4dp4rPq4Fd4KQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/MDQznkrkiyXwrjbX3WA1AA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/A2r-YTzWCYj6ItTi_ILQ7A2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/Ng9CuONRKei2uJcMpdsVgA2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/Q_AxWAge14pMs5iq0oQwLQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/ZJshvAu8TVVp4rPq4Fd4KQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/hYD2P4c5UB2aGfXRMrUjdw2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/muxiWf_jpqTgn4SMoDUcDQ2′]
[u’//read.qidian.com/chapter/MuRzJqCY6MyoLoerY3WDhg2/OQQ5jbADJjVp4rPq4Fd4KQ2′]
> …

爬取章節路徑的小爬蟲就寫好了,但我們的目的不僅於此,我們接下來使用這些地址來抓取內容:

2.7 章節頁面分析

我們接下來分析一下章節頁面,從章節頁面我們要獲取標題和內容。

如果說章節資訊爬取使用的 parser 方法,那麼我們可以給每一個章節內容的爬取寫一個方法,比如:parser_chapter,先看看章節頁面的具體情況:

?wx_fmt=png

可以看到,章節的整個內容在類名為 main-text-wrap 的 div 標籤內,標題是其中類名為j_chapterName的 h3 標籤,具體內容是類名為read-content j_readContent的 div 標籤。

試著把這些內容列印出來:

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

class XzxzbSpider(scrapy.Spider):
   name = ‘xzxzb’
   allowed_domains = [‘qidian.com’]
   start_urls = [‘https://book.qidian.com/info/1010780117/’]

   def parse(self, response):
       pages = response.xpath(‘//div[@id=”j-catalogWrap”]//ul[@class=”cf”]/li’)
       for page in pages:
           url = page.xpath(‘./child::a/attribute::href’).extract_first()
           # yield scrapy.Request(‘https:’ url, callback=self.parse_chapter)
           yield response.follow(url, callback=self.parse_chapter)
       pass

   def parse_chapter(self, response):
       title = response.xpath(‘//div[@class=”main-text-wrap”]//h3[@class=”j_chapterName”]/text()’).extract_first().strip()
       content = response.xpath(‘//div[@class=”main-text-wrap”]//div[@class=”read-content j_readContent”]’).extract_first().strip()
       print title
       # print content
       pass

上一步,我們獲取到了一個章節地址,從輸出內容來看是相對路徑,因此我們使用了yield response.follow(url, callback=self.parse_chapter),第二個引數是一個回撥函式,用來處理章節頁面,爬取到章節頁面後我們解析頁面和標題儲存到檔案。

next_page = response.urljoin(url)
yield scrapy.Request(next_page, callback=self.parse_chapter)

scrapy.Request 不同於使用 response.follow,需要通過相對路徑構造出絕對路徑,response.follow 可以直接使用相對路徑,因此就不需要呼叫 urljoin 方法了。

注意,response.follow 直接返回一個 Request 例項,可以直接通過 yield 進行返回。

資料獲取了之後是儲存,由於我們要的是 html 頁面,因此,我們就按標題儲存即可,程式碼如下:

   def parse_chapter(self, response):
       title = response.xpath(‘//div[@class=”main-text-wrap”]//h3[@class=”j_chapterName”]/text()’).extract_first().strip()
       content = response.xpath(‘//div[@class=”main-text-wrap”]//div[@class=”read-content j_readContent”]’).extract_first().strip()
       # print title
       # print content

       filename = ‘./down/%s.html’ % (title)
       with open(filename, ‘wb’) as f:
           f.write(content.encode(‘utf-8’))
       pass

至此,我們已經成功的抓取到了我們的資料,但還不能直接使用,需要整理和優化。

2.8 資料整理

首先,我們爬取下來的章節頁面排序不是很好,如果人工去排需要太多的時間精力;另外,章節內容包含許多額外的東西,閱讀體驗不好,我們需要優化內容的排版和可讀性。

我們先給章節排個序,因為目錄中的章節列表是按順序排列的,所以只需要給下載頁面名稱新增一個順序號就行了。

可是儲存網頁的程式碼是回撥函式,順序只是在處理目錄的時候能確定,回撥函式怎麼能知道順序呢?因此,我們要告訴回撥函式它處理章節的順序號,我們要給回撥函式傳參,修改後的程式碼是這樣的:

   def parse(self, response):
       pages = response.xpath(‘//div[@id=”j-catalogWrap”]//ul[@class=”cf”]/li’)
       for page in pages:
           url = page.xpath(‘./child::a/attribute::href’).extract_first()
           idx = page.xpath(‘./attribute::data-rid’).extract_first()
           # yield scrapy.Request(‘https:’ url, callback=self.parse_chapter)
           req = response.follow(url, callback=self.parse_chapter)
           req.meta[‘idx’] = idx
           yield req
       pass

   def parse_chapter(self, response):
       idx = response.meta[‘idx’]
       title = response.xpath(‘//div[@class=”main-text-wrap”]//h3[@class=”j_chapterName”]/text()’).extract_first().strip()
       content = response.xpath(‘//div[@class=”main-text-wrap”]//div[@class=”read-content j_readContent”]’).extract_first().strip()
       # print title
       # print content

       filename = ‘./down/%s_%s.html’ % (idx, title)
       cnt = ‘<h1>%s</h1> %s’ % (title, content)
       with open(filename, ‘wb’) as f:
           f.write(cnt.encode(‘utf-8’))
       pass

不知道大家注意到沒有,前面的分析中目錄已經提供了一個data_rid可以作為排序號,我們在目錄分析頁面獲取這個序號,然後通過 request 的 meta 傳入parse_chapter

parse_chapter中通過 response 的 meta 獲取傳入的引數,然後檔名中加入這個順序好完成了排序。另外,Sigil 找那個通過 H1 標籤來生成目錄,需要目錄的話,我們需要給內容新增一個 h1 標籤。

還有可讀性差的問題,也許我們下載的網頁可能會包含一些亂七八糟的東西,我們有很多辦法,也可以使用 readbility 等第三方庫,這裡就不深入了。

3 使用 Sigil 製作電子書

電子書的製作,完全就是工具的應用,非常簡單,這裡把流程過一下,大家根據興趣自行深入。

3.1 Sigil 簡介

Sigil 是一個多平臺的 ePub 電子書編輯器。官方網站:https://sigil-ebook.com/,下載頁面在 https://github.com/Sigil-Ebook/Sigil/releases,根據自己的需求下載,安裝很簡單就不囉嗦了。

3.2 ePub 電子書簡介

ePub(Electronic Publication 的縮寫,意為:電子出版),是一個自由的開放標準,屬於一種可以 “自動重新編排” 的內容;也就是文字內容可以根據閱讀裝置的特性,以最適於閱讀的方式顯示。

ePub 檔案內部使用了 XHTML 或 DTBook (一種由 DAISY Consortium 提出的 XML 標準)來展現文字、並以 zip 壓縮格式來包裹檔案內容。EPub 格式中包含了數位版權管理(DRM)相關功能可供選用。

3.3 載入 html 檔案

要製作 ePub 電子書,我們首先通過 Sigil 把我們的抓取的檔案載入到程式中,在新增檔案對話方塊中我們全選所有檔案:

?wx_fmt=png

內容都是 HTML 檔案,所以編輯、排版什麼的學習下 HTML。

3.4 製作目錄

檔案中存在 HTML 的 h 標籤時,點選生成目錄按鈕就可以自動生成目錄,我們在前面資料抓取時已經自動新增了 h1 標籤:

?wx_fmt=png

3.5 製作封面

?wx_fmt=png

封面本質上也是 HTML,可以編輯,也可以從頁面爬取,就留給大家自己實現吧。

?wx_fmt=png

3.6 編輯後設資料

編輯書名、作者等資訊:

?wx_fmt=png

3.6 輸出 ePub

編輯完成後儲存,取個名字:

?wx_fmt=png

輸出可以使用電子書閱讀軟體開啟檢視,我用的是 Calibre,還可以方便的轉換為相應的格式裝到 Kindle 中閱讀。

?wx_fmt=png?wx_fmt=png

整個過程就結束了,文章內程式碼提交到碼雲:https://goo.gl/yjGizR,接下來自由發揮,請開始你的表演。

參考資料

爬蟲 Scrapy 學習系列之一:Tutorial:https://goo.gl/LwqouP。

近期熱文

作為面試官,如何考察工程師的軟素質

談談 Java 記憶體模型

Jenkins 與 GitLab 的自動化構建之旅

通往高階 Java 開發的必經之路

談談原始碼洩露 · WEB 安全

用 LINQ 編寫 C# 都有哪些一招必殺的技巧?

機器學習面試乾貨精講


?wx_fmt=jpeg

「閱讀原文」看交流實錄,你想知道的都在這裡