今天早上起來,第一件事情就是理一理今天該作的事情,瞬間get到任務,寫一個只用python字符串內建函數的爬蟲,定義爲v1.0,開發中的版本號定義爲v0.x。數據存放?這個是一個練手的玩具,就寫在txt文本里吧。其實主要的不是學習爬蟲,而是依照這個需求鍛鍊下本身的編程能力,最重要的是要有一個清晰的思路(我在以這個目標努力着)。ok,主旨已經訂好了,開始‘擼串’了。html
目標網站:http://bohaishibei.com/post/category/main/(一個頗有趣的網站,一段話配一個圖,老有意思了~)網站形式以下:python
目標:把大的目標分爲幾個小的目標。由於第一次幹這個,因此對本身能力很清楚,因此完成順序由簡單到複雜。git
1.爬取一期的內容,包括標題,和圖片的urlgithub
2.把數據存在本地的txt文件中web
3.想爬多少就爬就爬少正則表達式
4.寫一個網站,展現一下。(純用於學習)sql
Let‘s 搞定它!數據庫
時間——9:14編程
把昨天晚上作的事情交代一下。昨天晚上寫的代碼實現了爬取一期裏的全部標題。api
第一步:
我用的是google瀏覽器,進入開發者模式,使用’頁面內的元素選擇器‘,先看一下內頁中的結構,找到咱們要的數據所在’標籤‘。
這裏咱們須要的博海拾貝一期的內容所有在<article class="article-content">這個標籤裏面,以下圖:
第一條紅線是:頁面內的元素選擇器
第二條是:內容所在標籤
第三條是:title
通過分析得出,我只要<article class="article-content">,這個標籤的內容:因此寫了下面的方法:
def content(html): # 內容分割的標籤 str = '<article class="article-content">' content = html.partition(str)[2] str1 = '<div class="article-social">' content = content.partition(str1)[0] return content # 獲得網頁的內容
這裏須要說一下:在寫這個爬蟲以前我就打算只用字符串的內置函數來處理匹配問題,因此我就上http://www.w3cschool.cc/python/進入到字符串頁面,大體看了一遍字符串的內建函數有哪些。
partition() 方法用來根據指定的分隔符將字符串進行分割。
若是字符串包含指定的分隔符,則返回一個3元的元組,第一個爲分隔符左邊的子串,第二個爲分隔符自己,第三個爲分隔符右邊的子串。
partition() 方法是在2.5版中新增的。參考:http://www.w3cschool.cc/python/att-string-partition.html
這樣我就獲得只有內容的字符串了,乾淨~
第二步:
獲得title的內容。title的格式以下,我只要’【2】‘後面的文字,後面的img暫時不考慮一步步的來。
<p>【2】這是我最近的狀態,請告訴我不是我一我的!</p><p><img src=http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8ifthnj30an0aot8w.jpg /></p><p>
我寫了下面的方法:
def title(content,beg = 0): # 思路是利用str.index()和序列的切片 try: title_list = [] while True: num1 = content.index('】',beg) num2 = content.index('</p>',num1) title_list.append(content[num1:num2]) beg = num2 except ValueError: return title_list
這裏用try....except是由於我不知道怎麼跳出循環。。。。求大神有更好的方法告訴我。
我這裏跳出循環用的是當拋出VlaueError異常就說明找不到了,那就返回列表。就跳出循環了。
num1是】的位置,num2是</p>的位置,而後用序列的切片,咔嚓咔嚓一下就是我想要的數據了。這裏須要注意的是:切片’要頭不要尾‘因此咱們的獲得的數據就是這個樣子的:
哎呀,這個是什麼鬼!要頭不要尾就是這個意思!
而後我就想:那就把num1加1不就完了嗎?我真是太天真了。。。。
請+3,我以爲原理是這樣的,這個是個中文字符!(求大神指點)
第三步:
交代清楚我昨天晚上作的事情了,記錄下時間——10:01,下面我要爬圖片的url了。這裏要說一下,若是要把圖片下下來,最重要的一步就是獲得url,而後下載下來保存到本地(用文本的IO)。
我先得到url,實現原理同獲取title,我在想,既然同樣卸載獲取title的方法裏好,仍是在寫一個方法好。我單獨寫了一個方法,可是其實就是複製了一下title的方法,改了下匹配的字符串,代碼以下:
def img(content,beg = 0): # 思路是利用str.index()和序列的切片 try: img_list = [] while True: src1 = content.index('http',beg) src2 = content.index('/></p>',src1) img_list.append(content[src1:src2]) beg = src2 except ValueError: return img_list
結果圖以下:
這裏發現,有的時候一個title會有不少個圖片。我思考以後有以下思路:
1.須要寫一個方法,當一個title出現多個圖片的時候,捕獲url。這個須要有一個判斷語句,當url長度大於一個url長度的時候,才須要調用這個函數。
2.多個圖片的url怎麼放?使用符號隔開存放仍是嵌套放入一個數組裏面?我這裏打算用’|‘隔開,這樣的話加一個判語句,或者先判斷一下url長度,均可以進行。
這個問題先放在這裏,由於當我要下載的時候這個url才須要過濾,因此先進行下一步,把數據存到本地txt文中,這裏在解決這個問題也不晚。
第四步:
把數據存到本地的txt中。Python文件IO參考資料:http://www.w3cschool.cc/python/python-files-io.html
這裏須要注意的是,文本寫入的時候記得close,還有就是注意打開文本的模式。
時間——11:05 吃個飯先
時間——11:44 回來了
這裏我考慮了一個問題,根據《編寫高質量代碼——改善python程序的91個建議》這本書中寫道的,字符串鏈接時,用jion()效率高於’+‘
因此我寫了以下代碼:
def data_out(data): #這裏寫成一個方法好處是,在寫入文本的時候就在這裏寫 fo = open("/home/qq/data.txt", "a+") #這裏注意從新寫一個地址 #for i,e in enumerate(data): fo.write("\n".join(data)); #print '第%d個,title:%s' % (i,e) # 關閉打開的文件 fo.close()
這樣形成了一個問題,看圖
形成最後一個和新的一個列表寫入時在同一行。同時用with....as更好。修改後代碼以下:
def data_out(data): #寫入文本 with open("/home/qq/foo.txt", "a+") as fo: fo.write('\n') fo.write("\n".join(data));
下面研究title和img以什麼樣的格式存入txt文本:
title$img
這裏我有一個概念混淆了,+和join()方法的效率問題主要在鏈接多個字符串的時候,我這個只用鏈接一次,不須要考慮這個問題。
def data_out(title, img): #寫入文本 with open("/home/qq/foo.txt", "a+") as fo: fo.write('\n') size = 0 for size in range(0, len(title)): fo.write(title[size]+'$'+img[size]+'\n');
文本中的內容以下:
願你貪吃不胖,願你懶惰不醜,願你深情不被辜負。$http://ww1.sinaimg.cn/mw690/005CfBldtw1etay8dl1bsj30c50cbq4m.jpg" 這是我最近的狀態,請告訴我不是我一我的!$http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8ifthnj30an0aot8w.jpg 引誘別人和你擊拳慶祝,而後偷偷把手勢變成二,就能夠合體成爲蝸牛cosplay……$http://ww2.sinaimg.cn/mw690/005CfBldtw1etay8fzm1sg30b40644qq.gif 原來蝸牛是醬紫吃東西的。。。。漲姿式!$http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8egg8vg30bo08ax6p.gif
寫入文本的最後,解決多個圖片的問題:
def many_img(data,beg = 0): #用於匹配多圖中的url try: many_img_str = '' while True: src1 = data.index('http',beg) src2 = data.index(' /><br /> <img src=',src1) many_img_str += data[src1:src2]+'|' # 多個圖片的url用"|"隔開 beg = src2 except ValueError: return many_img_str def data_out(title, img): #寫入文本 with open("/home/qq/data.txt", "a+") as fo: fo.write('\n') for size in range(0, len(title)): # 判斷img[size]中存在的是否是一個url if len(img[size]) > 70: img[size] = many_img(img[size])# 調用many_img()方法 fo.write(title[size]+'$'+img[size]+'\n')
輸出以下:
元氣少女陳意涵 by @TopFashionStyle$http://ww2.sinaimg.cn/mw690/005CfBldtw1etay848iktj30bz0bcq4x.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay83kv5pj30c10bkjsr.jpg|http://ww3.sinaimg.cn/mw690/005CfBldtw1etay82qdvsj30c10bkq3z.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay836z8lj30c00biq40.jpg|http://ww4.sinaimg.cn/mw690/005CfBldtw1etay8279qmj30ac0a0q3p.jpg|http://ww1.sinaimg.cn/mw690/005CfBldtw1etay81ug5kj30c50bnta6.jpg|http://ww2.sinaimg.cn/mw690/005CfBldtw1etay8161ncj30c20bgmyt.jpg|http://ww2.sinaimg.cn/mw690/005CfBldtw1etay804oy7j30bs0bgt9r.jpg|
暫時功能是實現了,後面遇到問題須要修改在改吧。。。。新手走一步看一步!!!
到此爲止,已經完成了前兩個簡單的計劃:
1.爬取一期的內容,包括標題,和圖片的url
2.把數據存在本地的txt文件中
所有代碼以下:
#coding:utf-8 import urllib ###### #爬蟲v0.1 利用urlib 和 字符串內建函數 ###### def getHtml(url): # 獲取網頁內容 page = urllib.urlopen(url) html = page.read() return html def content(html): # 內容分割的標籤 str = '<article class="article-content">' content = html.partition(str)[2] str1 = '<div class="article-social">' content = content.partition(str1)[0] return content # 獲得網頁的內容 def title(content,beg = 0): # 匹配title # 思路是利用str.index()和序列的切片 try: title_list = [] while True: num1 = content.index('】',beg)+3 num2 = content.index('</p>',num1) title_list.append(content[num1:num2]) beg = num2 except ValueError: return title_list def get_img(content,beg = 0): # 匹配圖片的url # 思路是利用str.index()和序列的切片 try: img_list = [] while True: src1 = content.index('http',beg) src2 = content.index('/></p>',src1) img_list.append(content[src1:src2]) beg = src2 except ValueError: return img_list def many_img(data,beg = 0): #用於匹配多圖中的url try: many_img_str = '' while True: src1 = data.index('http',beg) src2 = data.index(' /><br /> <img src=',src1) many_img_str += data[src1:src2]+'|' # 多個圖片的url用"|"隔開 beg = src2 except ValueError: return many_img_str def data_out(title, img): #寫入文本 with open("/home/qq/data.txt", "a+") as fo: fo.write('\n') for size in range(0, len(title)): # 判斷img[size]中存在的是否是一個url if len(img[size]) > 70: img[size] = many_img(img[size])# 調用many_img()方法 fo.write(title[size]+'$'+img[size]+'\n') content = content(getHtml("http://bohaishibei.com/post/10475/")) title = title(content) img = get_img(content) data_out(title, img) # 實現了爬的單個頁面的title和img的url並存入文本
時間——15:14
下面要從新分析網站,我已經能夠得到一期的內容了,我如今要獲得,其它期的url,這樣就想爬多少就爬多少了。
目標網址:http://bohaishibei.com/post/category/main/
按照上面的方法進入開發者模式分析網站結構,找出目標數據所在的標籤,擼它!
在首頁中須要的數據所有都在<div class="content">標籤裏,分隔方法以下:
def main_content(html): # 首頁內容分割的標籤 str = '<div class="content">' content = html.partition(str)[2] str1 = '</div>' content = content.partition(str1)[0] return content # 獲得網頁的內容
我暫時須要的數據:每一期的名字和每一期的url。
通過個人分析:該網站的每期的url格式是這樣的:"http://bohaishibei.com/post/10189/"只有數字是變化的。
後來我又發現,我想要的這兩個數據都在<h2>這個標籤下面,獲取每期url的方法以下:
def page_url(content, beg = 0): try: url = [] while True: url1 = content.index('<h2><a href="',beg)+13 url2 = content.index('" ',url1) url.append(content[url1:url2]) beg = url2 except ValueError: return url
title的格式,
我思考了一下,我要title其實沒什麼太大的意思,用戶有不可能說我要看那期,只須要輸入看多少期就能夠了,標題沒有什麼實際意義(不像內容中的title是幫助理解改張圖笑點的)。因此我打算在這個版本中只實現,你輸入想查看要多少期,就返回多少期!
那麼下面就須要一個策略了:
http://bohaishibei.com/post/category/main/ 共20期
http://bohaishibei.com/post/category/main/page/2/ 共20期
......
經查看,每頁都是20期
當你要查看的期數,超過20期的時候須要,增長page的數值,進入下一頁進行獲取
最後一頁爲這個:http://bohaishibei.com/post/category/main/page/48/
實現代碼,這個我要想想怎麼寫,我是第一次寫爬蟲,不要嘲諷我啊!
時間——17:09
感受快實現了,還在寫:
def get_order(num): page = num / 20 order = num % 20 # 超出一整頁的條目 for i in range(1, page+1): # 需這裏須要尾巴 url = 'http://bohaishibei.com/post/category/main/page/%d' % i print url if (i == page)&(order > 0): url = 'http://bohaishibei.com/post/category/main/page/%d' % (i+1) print url+",%d條" % order get_order(55)
運行結果:
http://bohaishibei.com/post/category/main/page/1 http://bohaishibei.com/post/category/main/page/2 http://bohaishibei.com/post/category/main/page/3,15條 2 ~~~~~~~~~~~~ 15
這裏我考慮是這樣的我須要重寫 page_url,須要多加一個參數,以下:
# 新增一個參數order,默認爲20 def page_url(content, order = 20, beg = 0): try: url = [] i = 0 while i < order: url1 = content.index('<h2><a href="',beg)+13 url2 = content.index('" ',url1) url.append(content[url1:url2]) beg = url2 i = i + 1 return url except ValueError: return url
下面這個方法是傳入參數num(須要多少期),一頁20期,返回每一期的url,代碼以下:
def get_order(num): # num表明獲取的條目數量 url_list = [] page = num / 20 order = num % 20 # 超出一整頁的條目 if num < 20: # 若是獲取的條目數量少於20(一頁20個),直接爬取第一頁的num條 url = 'http://bohaishibei.com/post/category/main' main_html = getHtml(url) clean_content = main_content(main_html) url_list = url_list + page_url(clean_content, num) for i in range(1, page+1): # 需這裏須要尾巴 url = 'http://bohaishibei.com/post/category/main/page/%d' % i # 爬取整頁的條目 main_html = getHtml(url) clean_content = main_content(main_html) url_list = url_list + page_url(clean_content) #獲取整夜 if (i == page)&(order > 0): # 爬到最後一頁,若是有超出一頁的條目則繼續怕order條 url = 'http://bohaishibei.com/post/category/main/page/%d' % (i+1) main_html = getHtml(url) clean_content = main_content(main_html) url_list = url_list + page_url(clean_content, order) #print len(page_url(clean_content, order)) return url_list
下面開始gogogo
order = get_order(21) for i in range(0, len(order)): #這個遍歷列表太醜了,改了: for i in order html = getHtml(order[i]) content_data = content(html) title_data = title(content_data) img_data = get_img(content_data) data_out(title_data, img_data)
ok了全部的代碼都寫完了
完整的代碼我已經上傳到個人github上了,地址爲:https://github.com/521xueweihan/PySpider/blob/master/Spider.py
這裏我在測試的時候有bug,由於該網站上有時候有的地方沒有img的地址。以下圖
個人代碼也就跟着出問題了,由於個人title和img列表數量不一了,而列表長度我是以title的len()爲準,結果就出現超出範圍了。
這裏記錄一下,而後我要去除bug了。
ok啦,bug消除了。我改了img的匹配方法以下:
def get_img(content,beg = 0): # 匹配圖片的url # 思路是利用str.index()和序列的切片 try: img_list = [] while True: src1 = content.index('src=',beg)+4 # 這樣寫就能夠匹配src="/" src2 = content.index('/></p>',src1) img_list.append(content[src1:src2]) beg = src2 except ValueError: return img_list
主函數:
order = get_order(30) # get_order方法接受參數,抓取多少期的數據 for i in order: # 遍歷列表的方法 html = getHtml(i) content_data = content(html) title_data = title(content_data) img_data = get_img(content_data) data_out(title_data, img_data)
爬下來的數據:
data.txt屬性(共30期的數據):
終於寫完了!
開始時間——9:14
寫爬蟲,吃飯,洗澡,休息了一會。
結束時間——21:02
呼,沒有半途而廢就滿足了,感受這樣把寫爬蟲的流程走了一遍下次再寫的話會快一些吧。
爬蟲是寫完了,可是用網站顯示尚未寫,明天看若是沒事就把網站寫出來。
圖片下載的功能,我尚未寫,等寫網站的時候再把它完善出來。
總結:
整個過程,純手寫,沒有參考別人的代碼。這一點能夠贊一下。
此次寫爬蟲就是強制本身不用正則表達式,和XPATH,發現有不少地方,用這兩個會很方便。這讓我下定決心去學正則表達式和Xpath,哈哈。體會過纔有深有感觸。
下一個目標是學習正則表達式和Xpath。一點點來,當我學完就來寫爬蟲v2.0,逐步完善吧,若是上來就要寫可貴,個人智商着急啊!
而後多看看別人的爬蟲,學習別人厲害的地方,提升本身。
歡迎你們指導交流。
完整的代碼我已經上傳到個人github上了,地址爲:https://github.com/521xueweihan/PySpider/blob/master/Spider.py
http://www.cnblogs.com/xueweihan/p/4592212.html
python編寫知乎爬蟲實踐
網絡爬蟲的基本工做流程以下:
在爬蟲系統中,待抓取URL隊列是很重要的一部分。待抓取URL隊列中的URL以什麼樣的順序排列也是一個很重要的問題,由於這涉及到先抓取那個頁面,後抓取哪一個頁面。而決定這些URL排列順序的方法,叫作抓取策略。下面重點介紹幾種常見的抓取策略:
瞭解了爬蟲的工做流程和爬取策略後,就能夠動手實現一個爬蟲了!那麼在python裏怎麼實現呢?
下面是一個僞代碼
import Queue initial_page = "https://www.zhihu.com/people/gaoming623" url_queue = Queue.Queue() seen = set() seen.insert(initial_page) url_queue.put(initial_page) while(True): #一直進行 if url_queue.size()>0: current_url = url_queue.get() #拿出隊例中第一個的url store(current_url) #把這個url表明的網頁存儲好 for next_url in extract_urls(current_url): #提取把這個url裏鏈向的url if next_url not in seen: seen.put(next_url) url_queue.put(next_url) else: break
若是你直接加工一下上面的代碼直接運行的話,你須要很長的時間才能爬下整個知乎用戶的信息,畢竟知乎有6000萬月活躍用戶。更別說Google這樣的搜索引擎須要爬下全網的內容了。那麼問題出如今哪裏?
須要爬的網頁實在太多太多了,而上面的代碼太慢太慢了。設想全網有N個網站,那麼分析一下判重的複雜度就是N*log(N),由於全部網頁要遍歷一次,而每次判重用set的話須要log(N)的複雜度。OK,我知道python的set實現是hash——不過這樣仍是太慢了,至少內存使用效率不高。
一般的判重作法是怎樣呢?Bloom Filter. 簡單講它仍然是一種hash的方法,可是它的特色是,它可使用固定的內存(不隨url的數量而增加)以O(1)的效率斷定url是否已經在set中。惋惜天下沒有白吃的午飯,它的惟一問題在於,若是這個url不在set中,BF能夠100%肯定這個url沒有看過。可是若是這個url在set中,它會告訴你:這個url應該已經出現過,不過我有2%的不肯定性。注意這裏的不肯定性在你分配的內存足夠大的時候,能夠變得很小不多。
# bloom_filter.py BIT_SIZE = 5000000 class BloomFilter: def __init__(self): # Initialize bloom filter, set size and all bits to 0 bit_array = bitarray(BIT_SIZE) bit_array.setall(0) self.bit_array = bit_array def add(self, url): # Add a url, and set points in bitarray to 1 (Points count is equal to hash funcs count.) # Here use 7 hash functions. point_list = self.get_postions(url) for b in point_list: self.bit_array[b] = 1 def contains(self, url): # Check if a url is in a collection point_list = self.get_postions(url) result = True for b in point_list: result = result and self.bit_array[b] return result def get_postions(self, url): # Get points positions in bit vector. point1 = mmh3.hash(url, 41) % BIT_SIZE point2 = mmh3.hash(url, 42) % BIT_SIZE point3 = mmh3.hash(url, 43) % BIT_SIZE point4 = mmh3.hash(url, 44) % BIT_SIZE point5 = mmh3.hash(url, 45) % BIT_SIZE point6 = mmh3.hash(url, 46) % BIT_SIZE point7 = mmh3.hash(url, 47) % BIT_SIZE return [point1, point2, point3, point4, point5, point6, point7]
BF詳細的原理參考我以前寫的文章:布隆過濾器(Bloom Filter)的原理和實現
用戶有價值的信息包括用戶名、簡介、行業、院校、專業及在平臺上活動的數據好比回答數、文章數、提問數、粉絲數等等。
用戶信息存儲的表結構以下:
CREATE DATABASE `zhihu_user` /*!40100 DEFAULT CHARACTER SET utf8 */; -- User base information table CREATE TABLE `t_user` ( `uid` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '用戶名', `brief_info` varchar(400) COMMENT '我的簡介', `industry` varchar(50) COMMENT '所處行業', `education` varchar(50) COMMENT '畢業院校', `major` varchar(50) COMMENT '主修專業', `answer_count` int(10) unsigned DEFAULT 0 COMMENT '回答數', `article_count` int(10) unsigned DEFAULT 0 COMMENT '文章數', `ask_question_count` int(10) unsigned DEFAULT 0 COMMENT '提問數', `collection_count` int(10) unsigned DEFAULT 0 COMMENT '收藏數', `follower_count` int(10) unsigned DEFAULT 0 COMMENT '被關注數', `followed_count` int(10) unsigned DEFAULT 0 COMMENT '關注數', `follow_live_count` int(10) unsigned DEFAULT 0 COMMENT '關注直播數', `follow_topic_count` int(10) unsigned DEFAULT 0 COMMENT '關注話題數', `follow_column_count` int(10) unsigned DEFAULT 0 COMMENT '關注專欄數', `follow_question_count` int(10) unsigned DEFAULT 0 COMMENT '關注問題數', `follow_collection_count` int(10) unsigned DEFAULT 0 COMMENT '關注收藏夾數', `gmt_create` datetime NOT NULL COMMENT '建立時間', `gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最後一次編輯', PRIMARY KEY (`uid`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用戶基本信息表';
網頁下載後經過XPath進行解析,提取用戶各個維度的數據,最後保存到數據庫中。
通常網站會從幾個維度來反爬蟲:用戶請求的Headers,用戶行爲,網站和數據加載的方式。從用戶請求的Headers反爬蟲是最多見的策略,不少網站都會對Headers的User-Agent進行檢測,還有一部分網站會對Referer進行檢測(一些資源網站的防盜鏈就是檢測Referer)。
若是遇到了這類反爬蟲機制,能夠直接在爬蟲中添加Headers,將瀏覽器的User-Agent複製到爬蟲的Headers中;或者將Referer值修改成目標網站域名。對於檢測Headers的反爬蟲,在爬蟲中修改或者添加Headers就能很好的繞過。
cookies = { "d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182", "login": "NzM5ZDc2M2JkYzYwNDZlOGJlYWQ1YmI4OTg5NDhmMTY=|1480901173|9c296f424b32f241d1471203244eaf30729420f0", "n_c": "1", "q_c1": "395b12e529e541cbb400e9718395e346|1479808003000|1468847182000", "l_cap_id": "NzI0MTQwZGY2NjQyNDQ1NThmYTY0MjJhYmU2NmExMGY=|1480901160|2e7a7faee3b3e8d0afb550e8e7b38d86c15a31bc", "d_c0": "AECA7v-aPwqPTiIbemmIQ8abhJy7bdD2VgE=|1468847182", "cap_id": "N2U1NmQwODQ1NjFiNGI2Yzg2YTE2NzJkOTU5N2E0NjI=|1480901160|fd59e2ed79faacc2be1010687d27dd559ec1552a" } headers = { "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.3", "Referer": "https://www.zhihu.com/" } r = requests.get(url, cookies = cookies, headers = headers)
還有一部分網站是經過檢測用戶行爲,例如同一IP短期內屢次訪問同一頁面,或者同一帳戶短期內屢次進行相同操做。
大多數網站都是前一種狀況,對於這種狀況,使用IP代理就能夠解決。這樣的代理ip爬蟲常常會用到,最好本身準備一個。有了大量代理ip後能夠每請求幾回更換一個ip,這在requests或者urllib2中很容易作到,這樣就能很容易的繞過第一種反爬蟲。目前知乎已經對爬蟲作了限制,若是是單個IP的話,一段時間系統便會提示異常流量,沒法繼續爬取了。所以代理IP池很是關鍵。網上有個免費的代理IP API: http://api.xicidaili.com/free2016.txt
import requests import random class Proxy: def __init__(self): self.cache_ip_list = [] # Get random ip from free proxy api url. def get_random_ip(self): if not len(self.cache_ip_list): api_url = 'http://api.xicidaili.com/free2016.txt' try: r = requests.get(api_url) ip_list = r.text.split('\r\n') self.cache_ip_list = ip_list except Exception as e: # Return null list when caught exception. # In this case, crawler will not use proxy ip. print e return {} proxy_ip = random.choice(self.cache_ip_list) proxies = {'http': 'http://' + proxy_ip} return proxies
爬蟲源代碼:zhihu-crawler 下載以後經過pip安裝相關三方包後,運行$ python crawler.py便可(喜歡的幫忙點個star哈,同時也方便看到後續功能的更新)
運行截圖: