1. 程式人生 > >Python 爬蟲簡單實現 (爬取下載連結)

Python 爬蟲簡單實現 (爬取下載連結)

原文地址:https://www.jianshu.com/p/8fb5bc33c78e

專案地址:https://github.com/Kulbear/All-IT-eBooks-Spider


這幾日和朋友搜尋東西的時候無意間發現了一個國外的存有大量PDF格式電子書的網站。其實我相當奇怪在國外版權管控如此嚴的環境下這個網站是如何拿到這麼多電子書的,而且全是正版樣式的PDF,目錄索引一應俱全,沒有任何影印和掃描的版本。

今天要做的事情就是爬取All IT eBooks這個網站上面PDF的下載連結了。

準備工作


  • 安裝Python 3.5.X

    因為Windows上配置Python稍微麻煩,我個人使用的是Anaconda提供的一站式安裝包,針對Windows平臺Anaconda提供了很傻瓜的一鍵安裝包
    
  • 或者選擇 Anaconda 下載頁

這個專案(姑且叫專案)的結構十分簡單,要爬取的網站結構設計也十分清晰,所以我們不需要用任何第三方庫!

分析網頁程式碼並提取


其實這個簡單的爬蟲需要做的事情僅僅是爬取目標網頁的原始碼(一般是HTML),提取自己需要的有效資訊,再做進一步使用。

開啟這個網頁,可以看到這個網站的設計十分簡潔和整齊,估計原始碼應該也是結構簡潔的


http://www.allitebooks.com/

將網頁往下拖可以看到在頁底有Pagination的按鈕,提供翻頁,翻頁後的連結為 http://www.allitebooks.com/page/數字頁碼/ 這個格式。

點選每本書或者標題以後,會進入到每本書的詳細資料頁面,並且有一個十分明顯的Download PDF的按鈕(這裡我就不截圖了)。

比如某本書詳細頁面的連結(不在上圖中,找了一本連結比較短的書):
http://www.allitebooks.com/big-data/

首先我們要拿到每個書detail頁面的連結,然後通過這個連結進入到具體的頁面,再找尋下載的連結。

多檢查幾個連結我們可以發現首頁上的每本書詳細頁面的連結很容易找到,每本書的內容都是一個article的node裡所包含的,例如:

<article id="post-23083" class="post-23083 post type-post status-publish format-standard has-post-thumbnail hentry category-networking-cloud-computing post-list clearfix">
    <div class="entry-thumbnail hover-thumb">
        <a href="http://www.allitebooks.com/ssl-vpn/" rel="bookmark">
            ![578ff04dd3488.jpg](http://upload-images.jianshu.io/upload_images/2527939-a975ffbeba6c3748.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) </a>
    </div>
    <!-- END .entry-thumbnail -->
    <div class="entry-body">
        <header class="entry-header">
            <h2 class="entry-title"><a href="http://www.allitebooks.com/ssl-vpn/" rel="bookmark">SSL VPN</a></h2>
            <!-- END .entry-title -->
            <div class="entry-meta">
                <span class="author vcard">
                <h5 class="entry-author">By: <a href="http://www.allitebooks.com/author/j-steinberg/" rel="tag">J. Steinberg</a>, <a href="http://www.allitebooks.com/author/joseph-steinberg/" rel="tag">Joseph Steinberg</a>, <a href="http://www.allitebooks.com/author/t-speed/" rel="tag">T. Speed</a>, <a href="http://www.allitebooks.com/author/tim-speed/" rel="tag">Tim Speed</a></h5>
                </span>
            </div>
            <!-- END .entry-meta -->
        </header>
        <!-- END .entry-header -->
        <div class="entry-summary">
            <p>This book is a business and technical overview of SSL VPN technology in a highly readable style. It provides a vendor-neutral introduction to SSL VPN technology for system architects, analysts and managers engaged in evaluating and planning
                an SSL VPN implementation. This book aimed at IT network professionals…</p>
        </div>
        <!-- END .entry-summary -->
    </div>
    <!-- END .entry-body -->
</article>

很容易就可以找到這本書的連結在第一層div的第一個子node上

<a href="http://www.allitebooks.com/ssl-vpn/" rel="bookmark">

仔細觀察整個網頁原始碼後發現,唯獨這個帶有書detail頁連結的tag裡有這條

rel="bookmark"

那麼現在就很簡單了,我們有以下幾個選擇來提取這個連結:

  1. BeautifulSoup
  2. 正則表示式(Regular Expression)
  3. 其他…

BeautifulSoup這裡不過多做敘述,簡單來說,這個庫可以幫你很好的分解HTML的DOM結構,而正則表示式則是Ultimate Solution,可以匹配任何符合條件的字串,這裡我們選用正則表示式(我也只學過皮毛,不過解決這次的問題只需要5分鐘入門級就可以),具體正則教程可以參見網上的資源,比如 這裡

先推薦一個線上檢測正則的網站 Regex101

'href="(.*)" rel="bookmark">'
Regex101.png

匹配剛才那個網頁連結所需要的正則表示式如上,現在我們來開始Python程式碼的部分:

import urllib.request
import re

BASE_URL = 'http://www.allitebooks.com'
BOOK_LINK_PATTERN = 'href="(.*)" rel="bookmark">'

req = urllib.request.Request(BASE_URL)
html = urllib.request.urlopen(req)
doc = html.read().decode('utf8')
# print(doc)
url_list = list(set(re.findall(BOOK_LINK_PATTERN, doc)))

以上程式碼能夠將網頁原始碼解碼並返回我們需要的url_list, 其中re.findall(…)這一部分的作用是,找到doc中所有符合BOOK_LINK_PATTERN的部分並return一個list出來,轉換為set只是為了去重,又在之後重新轉回為了list為了方便遍歷。

僅僅抓取第一頁顯然不夠,所以我們加入對頁碼的遍歷,如下:

....
i = 1    
while True:
    req = urllib.request.Request(BASE_URL)
    html = urllib.request.urlopen(req)
    doc = html.read().decode('utf8')
    # print(doc)
    url_list = list(set(re.findall(BOOK_LINK_PATTERN, doc)))
    # Do something here
    i += 1

這裡並沒有對可能出現的Error做處理,我們稍後補上。

至此,我們的程式已經可以抓取這個網站所有頁面裡的書detail頁面的連結了(理論上)

具體到每個頁面以後的工作變得十分簡單,通過訪問每本書的detail頁面,檢查原始碼,可以很輕鬆的提取出頁面裡Download PDF按鈕對應的下載連結。

<span class="download-links">
<a href="http://file.allitebooks.com/20160908/Expert Android Studio.pdf" target="_blank"><i class="fa fa-download" aria-hidden="true"></i> Download PDF <span class="download-size">(48.5 MB)</span></a>
</span>

其中,

<a href="http://file.allitebooks.com/20160908/Expert Android Studio.pdf" target="_blank"><i class="fa fa-download" aria-hidden="true"></i> Download PDF <span class="download-size">(48.5 MB)</span></a>

就是我們需要的部分了。

故技重施,使用如下的正則表示式匹配這一段HTML程式碼:

<a href="(http:\/\/file.*)" target="_blank">

這段程式碼就不分解放出了,自己動手吧(原始碼和Github連結在最後)。

(其實是因為我是寫完了整個程式碼以後才返回來寫這個文章,現在懶得拆了……)

小結


當然,這個簡單的程式只是一個最最基本的小爬蟲。離枝繁葉茂真正功能的爬蟲還差很多。多數網站都有多少不等的反爬蟲機制,比如單位時間內單一IP的方位次數限制等等。通常網站會有一個robots.txt檔案,規定了針對爬蟲的要求,比如能不能使用爬蟲。這個檔案一般在www.hostname.com/robots.txt這個格式的網址可以直接檢視,比如我們這次爬取的網站

http://www.allitebooks.com/robots.txt

應對不同網站的反爬蟲機制,我們可以選擇增加Header,隨機Header,隨機IP等很多方法來繞開,當你大量或者高頻爬取一些網站的同時,如果可以,別忘了給網站擁有者做一些貢獻(比如之前爬取Wiki的時候,捐贈了5刀…),以緩解網站作者維持伺服器的壓力。

原始碼


Github: https://github.com/JiYangE/All-IT-eBooks-Spider
請盡情的鞭笞Star我吧!

趁著午休的一小時趕工出來的程式碼,也沒備註重構修改過,結構略亂,單一指責根本沒有,我不管,能打仗的兵就是好兵,各位湊活一下看,邏輯非常簡單

檔案1 crawler.py

# -*- coding: utf-8 -*-
import re
import time
import urllib.request

import conf as cf

BASE_URL = 'http://www.allitebooks.com'

class MyCrawler:

    def __init__(self, base_url=cf.BASE_URL, header=cf.FAKE_HEADER, start_page=1):
        self.base_url = base_url
        self.start_page = start_page
        self.headers = header

    # 連結代理
    def build_proxy(self):
        proxy = cf.PROXY
        proxy_support = urllib.request.ProxyHandler(proxy)
        opener = urllib.request.build_opener(proxy_support)
        urllib.request.install_opener(opener)

    def fetch_book_name_list(self):
        while True:
            try:
                req = urllib.request.Request(
                    self.base_url + '/page/{}'.format(self.start_page), headers=self.headers)
                html = urllib.request.urlopen(req)
                doc = html.read().decode('utf8')
                alist = list(set(re.findall(cf.BOOK_LINK_PATTERN, doc)))
                print('Now working on page {}\n'.format(self.start_page))
                time.sleep(20)
                self.start_page += 1
                self.fetch_download_link(alist)
            except urllib.error.HTTPError as err:
                print(err.msg)
                break

    def fetch_download_link(self, alist):
        f = open('result.txt', 'a')
        for item in alist:
            req = urllib.request.Request(item)
            html = urllib.request.urlopen(req)
            doc = html.read().decode('utf8')
            url = re.findall(cf.DOWNLOAD_LINK_PATTERN, doc)[0]
            print('Storing {}'.format(url))
            f.write(url + '\n')
            time.sleep(7)
        f.close()

    def run(self):
        self.fetch_book_name_list()


if __name__ == '__main__':
    mc = MyCrawler()
    # mc.build_proxy()
    mc.run()

檔案2 conf.py

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

USER_AGENTS = [
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
    "Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
    "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
    "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
    "Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
    "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
    "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
    "Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
    "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
    "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
    "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
    "Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
    "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
    "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
    "Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
]

PROXY = {'http': "http://127.0.0.1:9743/"}

BOOK_LINK_PATTERN = 'href="(.*)" rel="bookmark">'
DOWNLOAD_LINK_PATTERN = '<a href="(http:\/\/file.*)" target="_blank">'

BASE_URL = 'http://www.allitebooks.com'

FAKE_HEADER = {
    'User-Agent': random.choice(USER_AGENTS),
    'Connection': 'keep-alive',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
    'Referer': 'http://www.allitebooks.com/',
    'Accept-Encoding': 'gzip, deflate, sdch',
    'Accept-Language': 'en-US,en;q=0.8',
}

執行結果檔案 result.txt 內容:

http://file.allitebooks.com/20160708/Functional Python Programming.pdf
http://file.allitebooks.com/20160709/Mastering JavaScript.pdf
http://file.allitebooks.com/20160708/ReSharper Essentials.pdf
http://file.allitebooks.com/20160714/Mastering Python.pdf
http://file.allitebooks.com/20160723/PHP in Action.pdf
http://file.allitebooks.com/20160709/Learning Google Apps Script.pdf
http://file.allitebooks.com/20160709/Mastering Yii.pdf
......

再廢話兩句


Python的功能日益強大起來,有很多現成的爬蟲框架可以學習,在熟練網路協議和抓取等基礎的網路知識以後,也可以試試學習一些較為完善的框架,比如Scrapy,詳情可以看崔慶才的總結

獲取授權

      </div>
    </div>
</div>