心血來潮, 想要了解一下爬蟲的基本原理, 本着目的驅動的原則, 想要把某美劇下載網站上的彙集下載連接都爬下來,我的收藏; 第一次寫, 不是什麼教程,只是記錄一下本身的思路和一些留着之後深刻的點, 寫的太亂,還請輕噴..html
既然是目的驅動,所以每一個涉及到的點在本文中都點到爲止,接下來我本身也會逐步更深刻的瞭解, 文章只是一個備忘錄, 以避免稍後遺忘;python
一個最簡單的爬蟲,一般有着類似的設計思路: 從一個頁面開始, 分析獲得全部感興趣的內容(保存)和連接, 再依次訪問這些連接進行一樣的操做, 直到不能繼續爲止:正則表達式
1. 記住已經訪問過的連接,再次遇到時不會訪問,避免陷入循環(x)數組
2. 以合適的結構和方式存儲獲取到的數據(x)性能優化
由於本文的目的和對應的網站的連接及富有規律性, 且要爬取得內容也很明確(而不是像搜索引擎同樣幾乎要保存全部內容),所以以上兩點均不涉及, 最後只是簡單的使用XML保存拿到的數據;app
一. 分析對應的站點:oop
版權問題, 這裏掩碼了目標網站;性能
本文要爬取的網站,首先有一個分頁的目錄, 每頁展現一部分美劇的簡介和詳情頁面的連接, 這個頁面的地址是: http://www.xxxxx.net/ddc1/page/x 末尾的x是從1開始的天然數;測試
詳情頁面的地址是 http://xxxxx.net/archives/xxxxx/ 末尾的xxxxx是隨機的1-5位數,無規律,並不是每一個數字都有頁面, 所以詳情頁面的連接,須要從目錄頁面提取,以避免作不少無用的工做;優化
目錄頁面, 每一部劇都在一個class爲thumbnail的div中:
<div class="thumbnail"> <a href="http://xxxxx.net/archives/23352/" rel="bookmark" title="生存指南第一季/全集Cooper Barrett.迅雷下載"> <img class="home-thumb" src="http://xxxxx.com/soohf5.jpg" width="140px" height="100px" alt="生存指南第一季/全集Cooper Barrett.迅雷下載"/> </a> </div>
所以能夠很方便的使用下面的正則表達式取出咱們須要的內容:
<div>.*?<a href="(.*?)" rel="bookmark" title="(.*?)">
同理,分析詳情頁面, 咱們獲得了下面的正則表達式,取出全部的下載連接:
<a href="((magnet|ed2k):.*?)">(.*?)</a>
.*? 這個問號,表示着咱們須要最小匹配, 由於頁面中有着不少循環的內容;
二. http請求
使用urllib2來進行http請求;
首先使用urllib2.Request來封裝一次請求, 其中第二個參數,設置了一個header,其中模擬了User-Agent爲IE, 簡單的以避免部分網站攔截咱們的爬蟲;
最後,對獲取到的page,作utf-8解碼, 成爲unicode (python內部的字符串都是unicode的)
self.header = { 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 9.0; Windows NT)' } url = self.DIC_LINK + str(page_no) req = Request(url, headers=self.header) response = urlopen(req) cur_page = response.read() unicode_page = cur_page.decode("utf-8")
urlopen方法可能會拋出異常(HTTPError, URLError), HTTPError繼承自URLError, 所以應優先捕獲HTTPError; HTTPError的code, 是一個整形數, 也就是咱們常見的404, 500等http返回碼, 通過測試,目錄列表的地址,當頁數超過了已有的最大頁數的時候,網站會返回404, 因此能夠把這個看成一個結束的標識;
三. 正則表達式
python的正則表達式用起來很順手, 引入 re 模塊就能夠愉快的使用了;
首先使用compile方法得到模式對象, 注意第二個參數 re.S ,使得 . 能夠匹配換行符
而後調用findall方法,就能得到全部的匹配了;
正則表達式中, 括號() 圍起來的,會成爲一個分組, 從左到又, 每遇到一個(, 則分組加1, 所以咱們獲得的items數組中, 每個元素,又是一個數組, 這個數組的第一個元素爲連接, 第二個元素爲title的值;
reg_str = r'<div>.*?<a href="(.*?)" rel="bookmark" title="(.*?)">' reg = re.compile(reg_str, re.S) items = re.findall(reg, unicode_page)
四. 結構
下面,能夠開始組裝邏輯了;
簡單的先將須要訪問的連接存在一個隊列中, 每次從隊列中取出連接訪問,獲取內容;
這樣就將爬取目錄頁面和詳情頁面的邏輯分開了;
下面是目錄頁面的方法:
def get_show_thread(self): end = False for page_no in range(1, 9999): if end: break url = self.DIC_LINK + str(page_no) req = Request(url, headers=self.header) flag = True while flag: try: response = urlopen(req) cur_page = response.read() unicode_page = cur_page.decode("utf-8") self.get_show(unicode_page) flag = False except HTTPError, e: print e if e.getcode() == 404: self.total = self.total_count end = True break except URLError: sleep(1) def get_show(self, unicode_page): reg_str = r'<div class="thumbnail">.*?<a href="(.*?)" rel="bookmark" title="(.*?)">' reg = re.compile(reg_str, re.S) items = re.findall(reg, unicode_page) self.total_count += len(items) for item in items: self.show_link.append(item)
使用一個數組show_link,保存要訪問的詳情頁面的連接;
接下來,主線程中, 不斷訪問show_link, 若是其中有連接, 便取出來訪問, 爬取詳情頁面的下載連接, 以後把這個連接從數組中刪除;
def get_download_link(self): while True: if len(self.show_link) > 0: url = self.show_link[0][0] notice = u'抓取' + url.encode('utf-8') notice += (r' ' + str(self.count) + r'/' + str(self.total_count)) print notice req = Request(url, headers=self.header) response = urlopen(req) cur_page = response.read() unicode_page = cur_page.decode("utf-8") reg_str = r'<a href="((magnet|ed2k):.*?)">(.*?)</a>' reg = re.compile(reg_str, re.S) items = re.findall(reg, unicode_page) self.save_links(items, self.show_link[0][1]) del self.show_link[0] else: sleep(0.5)
--TODO-- 最後,咱們按照格式,將獲得的數據保存爲xml文件 --TODO--
下一篇記錄下xml事;
上文中的代碼,是截取自完整的代碼, 裏面有不少變量和一些print, 主要是便於在查看運行進度(1000多個網頁,在一臺機器順序執行,且沒有作性能優化,所以是很費時間的....);