1. 程式人生 > >Python 實現一個自動下載小說的簡易爬蟲

Python 實現一個自動下載小說的簡易爬蟲

最近在學 Python,個人覺得 Python 是一種比較好玩的程式語言。快速看過一遍之後準備自己寫個小說爬蟲來鞏固下 Python 基礎知識。本人程式設計剛入門,很多東西理解還比較淺,寫下來是為了作為筆記方便以後回來優化改進,如果對本篇文章有好的建議或者有不足的地方,歡迎各位指出。

目錄

1. 前期知識準備

2. 選擇爬取的目標

我選取的目標是網上隨便找的一個免費小說網:https://www.qu.la(其實是一個盜版小說網站)。選取這個網站的原因是該網站的 html 標籤比較容易提取,適合練手,而且親測沒有任何反爬蟲機制,可以隨便蹂躪(咳咳,大家收斂一點,不要太用力)。壞處麼…就是網站伺服器不穩定,經常爬著爬著中斷了,報10053

錯誤,找不到原因只能重來,據說換成 Python 3不會出現這個問題?博主用的Python 2.7,還沒試過Python 3,有時間的童鞋可以試一下。

3. 實操

下面進入正題,很激動有木有!博主彷彿看見小說網彷彿像一個柔弱無助的小姑娘躲在牆角瑟瑟發抖,嘿嘿嘿…
古人云:“機會總是留給有準備的人的。”所以我們要先理清好我們的思路,再動手,博主的思路如下:

Created with Raphaël 2.1.2開始下載目標url的html獲取章節標題獲取正文內容儲存標題和正文確認是否有下一章?獲取下一章url結束yesno

3.1 下載目標 url 的 html

我選取了《超級神基因》作為要下載的小說進行演示。定義一個download

方法,傳入兩個引數。 url 為小說的第一章的網址:https://www.qu.la/book/25877/8923073.html,當網路出現錯誤時,重新發起請求,num_retries = 5默認同一連結請求不超過5次。該方法返回了網站的html程式碼。Python 程式碼如下:

import urllib2

def download(url, num_retries=5):
    """
    :param url: the fist chapter's url of novel like 'https://www.qu.la/book/25877/8923073.html'
    :param num_retries: times to retry to reconnect when fail to connect
    :return:html of url which be inputted
    """
print 'Start downloading:', url try: html = urllib2.urlopen(url).read() print 'Download finished:', url except urllib2.URLError as e: print 'Download fail.Download error:', e.reason html = None if num_retries > 0: print 'Retrying:', url html = download(url, num_retries - 1) return html

3.2 獲取每一章的標題和正文

這一步我們要檢視包含有標題和正文的標籤,將相關的標籤內容篩選出來。注意一定要將html程式碼下載下來再檢視,不要直接用瀏覽器的開發工具檢視原始碼。博主踩了這個坑,發現一直匹配不到相關的標籤,後來有前端老司機告訴我JavaScript程式碼可能會自動完成程式碼(好像是這麼個意思,博主前端 0 基礎),你在瀏覽器看到的程式碼很可能改變了。
將下載下來的html程式碼儲存為一個.html檔案,儲存html程式碼如下:

import re
import os

html = download('https://www.qu.la/book/25877/8923072.html')
with open(os.path.join(r'C:\Users\admin\Desktop', '123.html'), 'wb') as f:
    f.write(html)

檢視儲存的在桌面的123.html檔案,可以發現章節標題的標籤為<h1>
gettitle
使用正則表示式在html程式碼中匹配該標籤的內容,程式碼如下:

import re

def get_title(html):
    """Find Title of each chapter,return the title of chapter
    """
    title_regex = re.compile('<h1>(.*?)</h1>', re.IGNORECASE)
    title = str(title_regex.findall(html)[0])
    return title

同理,在html程式碼中查詢文字的相關標籤為<div id="content">
getcontent
然後這裡有個坑,用正則表示式居然匹配不到!!!博主一臉懵逼地研究了幾個小時未果,於是用了另一種方法:使用BeautifulSoup庫。話不多說,放程式碼:

from bs4 import BeautifulSoup

def get_content(html):
    """get content of each chapter from the html
    """
    soup = BeautifulSoup(html, 'html.parser')
    # fixed_html = soup.prettify()
    div = soup.find('div', attrs={'id': 'content'})
    [s.extract() for s in soup('script')]
    # print div
    content = str(div).replace('<br/>', '\n').replace('<div id="content">', '').replace('</div>', '').strip()
    return content

3.3 儲存標題和正文

接下來就是把上一步得到的標題和正文儲存到文件中去,博主偷懶把地址寫死了,這一步比較簡單,不多做解釋,看程式碼:

import re

def save(title, content):
    with open(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt', 'a+') as f:
        f.writelines(title + '\n')
        f.writelines(content + '\n')

3.4 獲取下一章的 url

html程式碼中找到下一章的連結:
下一章連結
老規矩,在html程式碼中匹配這個標籤,拿到href的內容,程式碼如下:

from bs4 import BeautifulSoup

def get_linkofnextchapter(html):
    """This method will get the url of next chapter
    :return: a relative link of the next chapter
    """
    soup = BeautifulSoup(html, 'html.parser')
    a = soup.find('a', attrs={'class': 'next', 'id': 'A3', 'target': '_top'})
    # print a['href']
    return a['href']

3.5 編寫啟動的方法

呼叫前面的方法,作為整個程式的入口,程式碼如下:

def begin(url):
    # make sure panth exited
    if not os.path.isdir(r'C:\Users\admin\Desktop\DNAofSuperGod'):
        os.mkdir(r'C:\Users\admin\Desktop\DNAofSuperGod')
    # remove old file and build a new one
    if os.path.isfile(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt'):
        os.remove(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt')
    html = download(url)
    # if html is None,download fail.
    if not html == None:
        title = get_title(html)
        print title
        content = get_content(html)
        save(title, content)
        print 'Have saved %s for you.' % title
        link = get_linkofnextchapter(html)
        # judge if has next chapter?
        if not re.match(r'./', link):
            nexturl = urlparse.urljoin(url, link)
            begin(nexturl)
        else:
            print 'Save finished!'
    else:
        print 'Download fail'

3.6 啟動爬蟲

終於到達最後一步啦,我們要啟動我們的爬蟲程式,呼叫程式碼很簡單:

url = 'https://www.qu.la/book/25877/8923072.html'
begin(url)

但是!如果順利的話,在程式下載到900多章的時候,你可以很幸福地看到程式報錯了!
下面這段是我複製的(我才不會傻傻的重新跑一遍程式呢)

RuntimeError: maximum recursion depth exceeded

找了度娘後發現這是python的保護機制,防止無限遞迴導致記憶體溢位,預設的遞迴深度是 1000,所以我們可以把這個預設值改大一點即可。

import sys

# change recursion depth as 10000(defult is 1000)
sys.setrecursionlimit(10000)
url = 'https://www.qu.la/book/25877/8923072.html'
begin(url)

3.7 附上完整程式碼

import urllib2
import re
import os
import urlparse

import sys
from bs4 import BeautifulSoup

# __author__:chenyuepeng

"""
This demon is a webspider to get a novel from https://www.qu.la
"""


def download(url, num_retries=5):
    """
    :param url: the fist chapter's url of novel like 'https://www.qu.la/book/25877/8923073.html'
    :param num_retries: times to retry to reconnect when fail to connect
    :return:html of url which be inputted
    """
    print 'Start downloading:', url
    try:
        html = urllib2.urlopen(url).read()
        print 'Download finished:', url
    except urllib2.URLError as e:
        print 'Download fail.Download error:', e.reason
        html = None
        if num_retries > 0:
            print 'Retrying:', url
            html = download(url, num_retries - 1)
    return html


def get_title(html):
    """Find Title of each chapter,return the title of chapter
    """
    title_regex = re.compile('<h1>(.*?)</h1>', re.IGNORECASE)
    title = str(title_regex.findall(html)[0])
    return title


def get_content(html):
    """get content of each chapter from the html
    """
    soup = BeautifulSoup(html, 'html.parser')
    # fixed_html = soup.prettify()
    div = soup.find('div', attrs={'id': 'content'})
    [s.extract() for s in soup('script')]
    # print div
    content = str(div).replace('<br/>', '\n').replace('<div id="content">', '').replace('</div>', '').strip()
    return content


def get_linkofnextchapter(html):
    """This method will get the url of next chapter
    :return: a relative link of the next chapter
    """
    soup = BeautifulSoup(html, 'html.parser')
    a = soup.find('a', attrs={'class': 'next', 'id': 'A3', 'target': '_top'})
    # print a['href']
    return a['href']


def save(title, content):
    with open(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt', 'a+') as f:
        f.writelines(title + '\n')
        f.writelines(content + '\n')


def begin(url):
    # make sure panth exited
    if not os.path.isdir(r'C:\Users\admin\Desktop\DNAofSuperGod'):
        os.mkdir(r'C:\Users\admin\Desktop\DNAofSuperGod')
    # remove old file and build a new one
    if os.path.isfile(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt'):
        os.remove(r'C:\Users\admin\Desktop\DNAofSuperGod\novel.txt')
    html = download(url)
    # if html is None,download fail.
    if not html == None:
        title = get_title(html)
        print title
        content = get_content(html)
        save(title, content)
        print 'Have saved %s for you.' % title
        link = get_linkofnextchapter(html)
        # judge if has next chapter?
        if not re.match(r'./', link):
            nexturl = urlparse.urljoin(url, link)
            begin(nexturl)
        else:
            print 'Save finished!'
    else:
        print 'Download fail'


# change recursion depth as 10000(defult is 900+)
sys.setrecursionlimit(10000)
url = 'https://www.qu.la/book/25877/8923072.html'
begin(url)

4. 優化

這是在初學Python後的練手專案,還有很多優化的空間,暫時先寫下來,留待以後改進。如果有好的建議或者相關的問題,歡迎在評論區留言討論。

  • 將單執行緒改為多執行緒或多程序,利用併發加快下載速度
  • 加入快取機制,將下載過的url快取到本地,如果程式中斷了不需要從頭開始下載,從快取中提取相關資訊即可