編寫Python腳本抓取網(wǎng)絡(luò)小說來制作自己的閱讀器
你是否苦惱于網(wǎng)上無法下載的“小說在線閱讀”內(nèi)容?或是某些文章的內(nèi)容讓你很有收藏的沖動(dòng),卻找不到一個(gè)下載的鏈接?是不是有種自己寫個(gè)程序把全部搞定的沖動(dòng)?是不是學(xué)了 python,想要找點(diǎn)東西大展拳腳,告訴別人“哥可是很牛逼的!”?那就讓我們開始吧! 哈哈~
好吧,我就是最近寫 Yii 寫多了,想找點(diǎn)東西調(diào)劑一下.... = =
本項(xiàng)目以研究為目的,所有版權(quán)問題我們都是站在作者的一邊,以看盜版小說為目的的讀者們請(qǐng)自行面壁!
說了這么多,我們要做的就是把小說正文的內(nèi)容從網(wǎng)頁上爬下來,我們的研究對(duì)象是全本小說網(wǎng)....再次聲明,不對(duì)任何版權(quán)負(fù)責(zé)....
一開始先做最基礎(chǔ)的內(nèi)容,就是把某一章的內(nèi)容抓取下來。
環(huán)境:Ubuntu, Python 2.7
基礎(chǔ)知識(shí)
這個(gè)程序涉及到的知識(shí)點(diǎn)有幾個(gè),在這里列出來,不詳細(xì)講,有疑問的直接百度會(huì)有一堆的。
1.urllib2 模塊的 request 對(duì)像來設(shè)置 HTTP 請(qǐng)求,包括抓取的 url,和偽裝瀏覽器的代理。然后就是 urlopen 和 read 方法,都很好理解。
2.chardet 模塊,用于檢測(cè)網(wǎng)頁的編碼。在網(wǎng)頁上抓取數(shù)據(jù)很容易遇到亂碼的問題,為了判斷網(wǎng)頁是 gtk 編碼還是 utf-8 ,所以用 chardet 的 detect 函數(shù)進(jìn)行檢測(cè)。在用 Windows 的同學(xué)可以在這里 http://download.csdn.net/detail/jcjc918/8231371 下載,解壓到 python 的 lib 目錄下就好。
3. decode 函數(shù)將字符串從某種編碼轉(zhuǎn)為 unicode 字符,而 encode 把 unicode 字符轉(zhuǎn)為指定編碼格式的字符串。
4. re 模塊正則表達(dá)式的應(yīng)用。search 函數(shù)可以找到和正則表達(dá)式對(duì)應(yīng)匹配的一項(xiàng),而 replace 則是把匹配到的字符串替換。
思路分析:
我們選取的 url 是 http://www.quanben.com/xiaoshuo/0/910/59302.html,斗羅大陸的第一章。你可以查看網(wǎng)頁的源代碼,會(huì)發(fā)現(xiàn)只有一個(gè) content 標(biāo)簽包含了所有章節(jié)的內(nèi)容,所以可以用正則把 content 的標(biāo)簽匹配到,抓取下來。試著把這一部分內(nèi)容打印出來,會(huì)發(fā)現(xiàn)很多 <br /> 和  ,<br /> 要替換成換行符,   是網(wǎng)頁中的占位符,即空格,替換成空格就好。這樣一章的內(nèi)容就很美觀的出來了。完整起見,同樣用正則把標(biāo)題爬下來。
程序
# -*- coding: utf-8 -*- import urllib2 import re import chardet class Book_Spider: def __init__(self): self.pages = [] # 抓取一個(gè)章節(jié) def GetPage(self): myUrl = "http://www.quanben.com/xiaoshuo/0/910/59302.html"; user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } request = urllib2.Request(myUrl, headers = headers) myResponse = urllib2.urlopen(request) myPage = myResponse.read() #先檢測(cè)網(wǎng)頁的字符編碼,最后統(tǒng)一轉(zhuǎn)為 utf-8 charset = chardet.detect(myPage) charset = charset['encoding'] if charset == 'utf-8' or charset == 'UTF-8': myPage = myPage else: myPage = myPage.decode('gb2312','ignore').encode('utf-8') unicodePage = myPage.decode("utf-8") try: #抓取標(biāo)題 my_title = re.search('<h1>(.*?)</h1>',unicodePage,re.S) my_title = my_title.group(1) except: print '標(biāo)題 HTML 變化,請(qǐng)重新分析!' return False try: #抓取章節(jié)內(nèi)容 my_content = re.search('<div.*?id="htmlContent" class="contentbox">(.*?)<div',unicodePage,re.S) my_content = my_content.group(1) except: print "內(nèi)容 HTML 變化,請(qǐng)重新分析!" return False #替換正文中的網(wǎng)頁代碼 my_content = my_content.replace("<br />","\n") my_content = my_content.replace(" "," ") #用字典存儲(chǔ)一章的標(biāo)題和內(nèi)容 onePage = {'title':my_title,'content':my_content} return onePage # 用于加載章節(jié) def LoadPage(self): try: # 獲取新的章節(jié) myPage = self.GetPage() if myPage == False: print '抓取失??!' return False self.pages.append(myPage) except: print '無法連接服務(wù)器!' #顯示一章 def ShowPage(self,curPage): print curPage['title'] print curPage['content'] def Start(self): print u'開始閱讀......\n' #把這一頁加載進(jìn)來 self.LoadPage() # 如果self的pages數(shù)組中存有元素 if self.pages: nowPage = self.pages[0] self.ShowPage(nowPage) #----------- 程序的入口處 ----------- print u""" --------------------------------------- 程序:閱讀呼叫轉(zhuǎn)移 版本:0.1 作者:angryrookie 日期:2014-07-05 語言:Python 2.7 功能:按下回車瀏覽章節(jié) --------------------------------------- """ print u'請(qǐng)按下回車:' raw_input() myBook = Book_Spider() myBook.Start()
程序運(yùn)行完在我這里可是很好看的,不信請(qǐng)看:^_^
理所當(dāng)然地,接下來我們要把整本小說都爬下來。首先,我們要把程序從原來的讀完一章就結(jié)束,改成讀完一章之后可以繼續(xù)進(jìn)行下一章的閱讀。
注意到每個(gè)小說章節(jié)的網(wǎng)頁下面都有下一頁的鏈接。通過查看網(wǎng)頁源代碼,稍微整理一下( 不顯示了),我們可以看到這一部分的 HTML 是下面這種格式的:
<div id="footlink"> <script type="text/javascript" charset="utf-8" src="/scripts/style5.js"></script> <a >上一頁</a> <a >返回目錄</a> <a >下一頁</a> </div>
上一頁 、返回目錄、下一頁都在一個(gè) id 為 footlink 的 div 中,如果想要對(duì)每個(gè)鏈接進(jìn)行匹配的話,會(huì)抓取到網(wǎng)頁上大量的其他鏈接,但是 footlink 的 div 只有一個(gè)??!我們可以把這個(gè) div 匹配到,抓下來,然后在這個(gè)抓下來的 div 里面再匹配 <a> 的鏈接,這時(shí)就只有三個(gè)了。只要取最后一個(gè)鏈接就是下一頁的 url 的,用這個(gè) url 更新我們抓取的目標(biāo) url ,這樣就能一直抓到下一頁。用戶閱讀邏輯為每讀一個(gè)章節(jié)后,等待用戶輸入,如果是 quit 則退出程序,否則顯示下一章。
基礎(chǔ)知識(shí):
上一篇的基礎(chǔ)知識(shí)加上 Python 的 thread 模塊.
源代碼:
# -*- coding: utf-8 -*- import urllib2 import re import thread import chardet class Book_Spider: def __init__(self): self.pages = [] self.page = 1 self.flag = True self.url = "http://www.quanben.com/xiaoshuo/10/10412/2095096.html" # 將抓取一個(gè)章節(jié) def GetPage(self): myUrl = self.url user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } req = urllib2.Request(myUrl, headers = headers) myResponse = urllib2.urlopen(req) myPage = myResponse.read() charset = chardet.detect(myPage) charset = charset['encoding'] if charset == 'utf-8' or charset == 'UTF-8': myPage = myPage else: myPage = myPage.decode('gb2312','ignore').encode('utf-8') unicodePage = myPage.decode("utf-8") # 找出 id="content"的div標(biāo)記 try: #抓取標(biāo)題 my_title = re.search('<h1>(.*?)</h1>',unicodePage,re.S) my_title = my_title.group(1) except: print '標(biāo)題 HTML 變化,請(qǐng)重新分析!' return False try: #抓取章節(jié)內(nèi)容 my_content = re.search('<div.*?id="htmlContent" class="contentbox">(.*?)<div',unicodePage,re.S) my_content = my_content.group(1) except: print "內(nèi)容 HTML 變化,請(qǐng)重新分析!" return False my_content = my_content.replace("<br />","\n") my_content = my_content.replace(" "," ") #用字典存儲(chǔ)一章的標(biāo)題和內(nèi)容 onePage = {'title':my_title,'content':my_content} try: #找到頁面下方的連接區(qū)域 foot_link = re.search('<div.*?class="chapter_Turnpage">(.*?)</div>',unicodePage,re.S) foot_link = foot_link.group(1) #在連接的區(qū)域找下一頁的連接,根據(jù)網(wǎng)頁特點(diǎn)為第三個(gè) nextUrl = re.findall(u'<a.*?href="(.*?)".*?>(.*?)</a>',foot_link,re.S) nextUrl = nextUrl[2][0] # 更新下一次進(jìn)行抓取的鏈接 self.url = nextUrl except: print "底部鏈接變化,請(qǐng)重新分析!" return False return onePage # 用于加載章節(jié) def LoadPage(self): while self.flag: if(len(self.pages) - self.page < 3): try: # 獲取新的頁面 myPage = self.GetPage() if myPage == False: print '抓取失?。? self.flag = False self.pages.append(myPage) except: print '無法連接網(wǎng)頁!' self.flag = False #顯示一章 def ShowPage(self,curPage): print curPage['title'] print curPage['content'] print "\n" user_input = raw_input("當(dāng)前是第 %d 章,回車讀取下一章或者輸入 quit 退出:" % self.page) if(user_input == 'quit'): self.flag = False print "\n" def Start(self): print u'開始閱讀......\n' # 新建一個(gè)線程 thread.start_new_thread(self.LoadPage,()) # 如果self的page數(shù)組中存有元素 while self.flag: if self.page <= len(self.pages): nowPage = self.pages[self.page-1] self.ShowPage(nowPage) self.page += 1 print u"本次閱讀結(jié)束" #----------- 程序的入口處 ----------- print u""" --------------------------------------- 程序:閱讀呼叫轉(zhuǎn)移 版本:0.2 作者:angryrookie 日期:2014-07-07 語言:Python 2.7 功能:按下回車瀏覽下一章節(jié) --------------------------------------- """ print u'請(qǐng)按下回車:' raw_input(' ') myBook = Book_Spider() myBook.Start()
現(xiàn)在這么多小說閱讀器,我們只需要把我們要的小說抓取到本地的 txt 文件里就好了,然后自己選個(gè)閱讀器看,怎么整都看你了。
其實(shí)上個(gè)程序我們已經(jīng)完成了大部分邏輯,我們接下來的改動(dòng)只需要把抓取到每一章的時(shí)候不用顯示出來,而是存入 txt 文件之中。另外一個(gè)是程序是不斷地根據(jù)下一頁的 Url 進(jìn)行抓取的,那么什么時(shí)候結(jié)束呢?注意當(dāng)?shù)竭_(dá)小說的最后一章時(shí)下一頁的鏈接是和返回目錄的鏈接是一樣的。所以我們抓取一個(gè)網(wǎng)頁的時(shí)候就把這兩個(gè)鏈接拿出來,只要出現(xiàn)兩個(gè)鏈接一樣的時(shí)候,就停止抓取。最后就是我們這個(gè)程序不需要多線程了,我們只要一個(gè)不斷在抓取小說頁面的線程就行了。
不過,小說章節(jié)多一點(diǎn)時(shí)候,等待完成的時(shí)間會(huì)有點(diǎn)久。目前就不考慮這么多了,基本功能完成就 OK....
基礎(chǔ)知識(shí):前面的基礎(chǔ)知識(shí) - 多線程知識(shí) + 文件操作知識(shí)。
源代碼:
# -*- coding:utf-8 -*- import urllib2 import urllib import re import thread import chardet class Book_Spider: def __init__(self): self.pages = [] self.page = 1 self.flag = True self.url = "http://www.quanben.com/xiaoshuo/0/910/59302.html" # 將抓取一個(gè)章節(jié) def GetPage(self): myUrl = self.url user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers = { 'User-Agent' : user_agent } req = urllib2.Request(myUrl, headers = headers) myResponse = urllib2.urlopen(req) myPage = myResponse.read() charset = chardet.detect(myPage) charset = charset['encoding'] if charset == 'utf-8' or charset == 'UTF-8': myPage = myPage else: myPage = myPage.decode('gb2312','ignore').encode('utf-8') unicodePage = myPage.decode("utf-8") # 找出 id="content"的div標(biāo)記 try: #抓取標(biāo)題 my_title = re.search('<h1>(.*?)</h1>',unicodePage,re.S) my_title = my_title.group(1) except: print '標(biāo)題 HTML 變化,請(qǐng)重新分析!' return False try: #抓取章節(jié)內(nèi)容 my_content = re.search('<div.*?id="htmlContent" class="contentbox">(.*?)<div',unicodePage,re.S) my_content = my_content.group(1) except: print "內(nèi)容 HTML 變化,請(qǐng)重新分析!" return False my_content = my_content.replace("<br />","\n") my_content = my_content.replace(" "," ") #用字典存儲(chǔ)一章的標(biāo)題和內(nèi)容 onePage = {'title':my_title,'content':my_content} try: #找到頁面下方的連接區(qū)域 foot_link = re.search('<div.*?class="chapter_Turnpage">(.*?)</div>',unicodePage,re.S) foot_link = foot_link.group(1) #在連接的區(qū)域找下一頁的連接,根據(jù)網(wǎng)頁特點(diǎn)為第三個(gè) nextUrl = re.findall(u'<a.*?href="(.*?)".*?>(.*?)</a>',foot_link,re.S) #目錄鏈接 dir_url = nextUrl[1][0] nextUrl = nextUrl[2][0] # 更新下一次進(jìn)行抓取的鏈接 self.url = nextUrl if(dir_url == nextUrl): self.flag = False return onePage except: print "底部鏈接變化,請(qǐng)重新分析!" return False # 用于加載章節(jié) def downloadPage(self): f_txt = open(u"斗羅大陸.txt",'w+') while self.flag: try: # 獲取新的頁面 myPage = self.GetPage() if myPage == False: print '抓取失?。? self.flag = False title = myPage['title'].encode('utf-8') content = myPage['content'].encode('utf-8') f_txt.write(title + '\n\n') f_txt.write(content) f_txt.write('\n\n\n') print "已下載 ",myPage['title'] except: print '無法連接服務(wù)器!' self.flag = False f_txt.close() def Start(self): print u'開始下載......\n' self.downloadPage() print u"下載完成" #----------- 程序的入口處 ----------- print u""" --------------------------------------- 程序:閱讀呼叫轉(zhuǎn)移 版本:0.3 作者:angryrookie 日期:2014-07-08 語言:Python 2.7 功能:按下回車開始下載 --------------------------------------- """ print u'請(qǐng)按下回車:' raw_input(' ') myBook = Book_Spider() myBook.Start()
相關(guān)文章
Django 實(shí)現(xiàn)將圖片轉(zhuǎn)為Base64,然后使用json傳輸
這篇文章主要介紹了Django 實(shí)現(xiàn)將圖片轉(zhuǎn)為Base64,然后使用json傳輸,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03pytest生成Allure報(bào)告以及查看報(bào)告的實(shí)現(xiàn)
本文主要介紹了pytest生成Allure報(bào)告以及查看報(bào)告的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Python 異步之如何保護(hù)任務(wù)免于取消詳解
這篇文章主要為大家介紹了Python 異步之如何保護(hù)任務(wù)免于取消示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03python之tensorflow手把手實(shí)例講解斑馬線識(shí)別實(shí)現(xiàn)
目前智慧城市的發(fā)展,人們生活處處有科技,比如人臉識(shí)別,智慧交通,無人駕駛等前沿的科技產(chǎn)品也都融入了人們生活中;本篇文章帶你從頭開始實(shí)現(xiàn)斑馬線識(shí)別2021-09-09python利用urllib實(shí)現(xiàn)爬取京東網(wǎng)站商品圖片的爬蟲實(shí)例
下面小編就為大家?guī)硪黄猵ython利用urllib實(shí)現(xiàn)爬取京東網(wǎng)站商品圖片的爬蟲實(shí)例。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-08-08