標籤:

爬取豆瓣有關張國榮日記(二)—— 策略源碼知識點

本來想用Scrapy來爬的,結果連續被ban。

設置動態UA、加Cookies、用vpn也無濟於事,輾轉一天多,累覺不愛。

反爬機制不要太強啊,給豆瓣小組點個贊,服!!

不過,最後還是用一般方法的解決了

說來也奇怪,大概因為Scrapy是非同步多線程,所以容易被發現吧。

一、目標

爬取豆瓣所有關於張國榮的日記

1、獲取每一篇標題、作者、鏈接、點贊數、發布時間,將數據存入excel

2、獲取所有日記內容,存入txt

3、將所有文章匯總,jieba分詞,做成詞雲圖

二、過程

1、分析網頁及源碼

豆瓣首頁搜索框中輸入 張國榮

查看源碼,分析得到URL

順帶分析下其它

第二頁,start=20

第三頁,start=40

發現規律了么,每一次刷新,URL=https://www.douban.com/j/search?q=張國榮&start=n&cat=1015, 其中n=0,20,40...那我們完全可以直接這樣構造,每一次獲取20項內容(為一個list),不用一個一個來了,手動試了下共有n最大為2000

2、具體步驟及問題解決

a、構造URL,解析json格式

代碼這樣,注意range函數:

for i in range(0,2001,20):n response=self.get_source(url=https://www.douban.com/j/search?q=張國榮+&start=+str(i)+&cat=1015)n main=json.loads(response.text)[items]n mains.append(main)n

b、解析每一項獲得id,由id構造每一篇鏈接

直接在源碼上看的話,眼花,我是for循環列印到IDE中看的

用到BeautifulSoup和正則:

links=[]n#get_main()是上面解析數據的函數n#得到包含20項內容的大listnmains=self.get_main()nfor main in mains:n #每一頁有20項n for j in range(0,20):n #先找到所有h3標籤n soup=BeautifulSoup(main[j],lxml).find(h3)n #在其中匹配idn pattern=re.compile(r<a href=.*?sid:(.*?)>(.*?)</a>, re.S)n items=re.findall(pattern, str(soup))n for item in items:n id=item[0][0:-3].strip()n link=https://www.douban.com/note/+str(id)+/n #所有鏈接放入list中n links.append(link)n time.sleep(0.2)n

看樣子是沒錯,然而,運行時卻出問題。

List index out of range(抱歉忘記截圖了)

搞了好久,終於發現了,比如start=120時:

也就是說,並不是「每一頁」都有20項,有的少於20項。我以為120這裡是個特殊,後面發現很多別的頁面也有這樣的狀況。不能愉快地用range函數for循環了,好氣呀。

還好本寶寶機智,想到itertools迭代器,可是超出索引範圍會報IndexError。

想了下,加個try...except...,解決!

#記得導入itertools模塊nimport itertoolsnlinks=[]nmains=self.get_main()nfor main in mains:n try:n for j in itertools.count(0):n xxxxxxxx(和上面那一堆一樣)n except IndexError:n continuen

c、提取每一頁中標題、作者、日期、內容、點贊數等信息

具體項目放入名為data的list中,多個data放入名為container的大list中

程序跑起來沒有問題,可是打開點贊數那一欄,卻是空的。跑去分析源碼,發現原來沒那麼簡單。如何入手解決,想了很久。

突然想到之前貌似看到過什麼:

發現了么,原來豆瓣分pc端和移動端,移動端是m.douban.com形式(m 即為mobile),試了一下,這兩個頁面還是有些不一樣的。

既然pc端獲取不到點贊數,那麼何不試試移動端。於是出現這樣:

果然有差別,看到紅框框里這個data-needlogin應該覺悟,這是需要登錄才能看到點贊數呀,於是模擬登錄。試了才知道,post方法根本就行不通。

沒轍了么,別忘了我們還有個簡單的,用Cookies模擬登錄也可以呀,加Session保持會話。

#這裡用的首頁的cookiesncookies=bid=6xgbcS6Pqds; ll="118318"; viewed="3112503"; gr_user_id=660f3409-0b65-4195-9d2f-a3c71573b40f; ct=y; ps=y; _ga=GA1.2.325764598.1467804810; _vwo_uuid_v2=112D0E7472DB37089F4E96B7F4E5913D|faf50f21ff006f877c92e097c4f2819c; ap=1; push_noty_num=0; push_doumail_num=0; _pk_ref.100001.8cb4=%5B%22%22%2C%22%22%2C1491576344%2C%22http%3A%2F%2Fwww.so.com%2Flink%3Furl%3Dhttp%253A%252F%252Fwww.douban.com%252F%26q%3D%25E8%25B1%2586%25E7%2593%25A3%26ts%3D1491459621%26t%3Df67ffeb4cd66c531150a172c69796e0%26src%3Dhaosou%22%5D; __utmt=1; _pk_id.100001.8cb4=41799262efd0b923.1467804804.35.1491576361.1491567557.; _pk_ses.100001.8cb4=*; __utma=30149280.325764598.1467804810.1491566154.1491576346.34; __utmb=30149280.3.10.1491576346; __utmc=30149280; __utmz=30149280.1491469694.24.15.utmcsr=baidu|utmccn=(organic)|utmcmd=organic; __utmv=30149280.12683; dbcl2="126831173:APSgA3NPab8"nheaders={User_Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36,Cookie:cookies}ns=requests.Session()nresponse=s.get(url,headers=headers)nrequests.adapters.DEFAULT_RETRIES=5n

然後就是這樣,加Cookies模擬登錄後,用pc端的URL也可以(後面都抓的pc端哦~)。

到了提取具體信息的環節。還需要要注意的是,「喜歡」那一欄可能沒有數據

而且分析源碼可以知道,有人點贊的有<span class="fav-num".*?>(.*?)</span>這一項,點贊為0的是沒有這一項的。

陷阱真多-_-|| 所以分情況討論匹配正則。

links=self.get_link()ncontainer=[]nn=1nfor link in links:n html=self.get_source(link).textn data=[]n #得先判斷有無點贊n patternNum=re.compile(rclass="fav-num",re.S)n Num=re.search(patternNum,html)n if Num:n pattern=re.compile(r<h1>(.*?)</h1>.*?<a href=.*?class="note-author">(.*?)</a>.*?<span class="pub-date">(.*?)</span>.*?<div class="note" id="link-report">(.*?)</div>.*?<span class="fav-num".*?>(.*?)</span>,re.S)n items=re.findall(pattern,html)n for item in items:n data.append(item[0])n data.append(link)n data.append(item[1])n data.append(item[2])n data.append(item[4])n data.append(self.tool.replace(item[3]))n else:n pattern=re.compile(r<h1>(.*?)</h1>.*?<a href=.*?class="note-author">(.*?)</a>.*?<span class="pub-date">(.*?)</span>.*?<div class="note" id="link-report">(.*?)</div>,re.S)n items=re.findall(pattern, html)n for item in items:n data.append(item[0])n data.append(link)n data.append(item[1])n data.append(item[2])n #無點贊時用0代替n data.append(0)n data.append(self.tool.replace(item[3]))n container.append(data)n time.sleep(0.5)n n+=1n

細心的盆友可能會叫住了,等等,這個self.tool.replace()怎麼回事?

應該想到,豆瓣日記除了文字,還有部分可能是含圖片甚至音頻等

所以定義了一個Tool類,清洗多餘的div、img、td等標籤

class Tool():n def replace(self,x):n x=re.sub(re.compile(<br>|</br>||<p>|</p>|<td>|</td>|<tr>|</tr>|</a>|<table>|</table>), "", x)n x=re.sub(re.compile(<div.*?>|<img.*?>|<a.*?>|<td.*?>), "", x)n return x.strip()nnclass Spider():n def __init__(self):n self.tool=Tool()n

d、保存數據入excel和txt

目前獲得標題、作者、鏈接、發布時間、點贊數、文章內容共6項。

我們將前5項錄入excel,最後一項存入txt。

可以先將文章錄入txt,接著list中去除文章一項, 最後錄入excel。用到remove方法。

一定警惕list=list.remove(i)這種寫法!

為什麼呢,看下面:

list=list.remove(i)其實是默認返回None,然後。。。就是個大坑啊,基礎不牢地動山搖論小白學習的心酸血淚史≧﹏≦

e、結巴分詞及詞雲圖

這回共2000多篇文章,字數太多不可能再用語料庫在線統計詞頻,所以採用統計權重的方法,再乘以10000放大。

這裡參考了博客:用jieba分詞提取關鍵詞做漂亮的詞雲

取了權重最大的前150個詞(後面作詞雲時又相應去掉了一些)

import jieba.analysenf=open(rF:DesktopDouBan.txt,r)ncontent=f.read()ntry:n jieba.analyse.set_stop_words(rF:DesktopTingYong.txt)n tags=jieba.analyse.extract_tags(content,topK=150, withWeight=True)n for item in tags:n print item[0]+t+str(int(item[1]*10000))nfinally:n f.close()n

TingYong.txt是停用詞表,可以在網上下載到,還可以自己修改添加。

詞雲就差不多和之前一樣,可見文章 Scrapy爬簡書30日熱門 —— 總是套路留人心

三、代碼

完整版代碼我放github上了:LUCY78765580/Python-web-scraping

豆瓣反爬比較厲害,半夜抓取可能比較好。

四、總結

最後總結一下本文關鍵

1、源碼分析:分析網站及URL結構;分析出豆瓣不僅有pc端還有移動端,而且兩者頁面不太一樣;分析出不登錄的話是獲取不到點贊數的,同時有無點贊頁面代碼是不一樣的。

2、抓包獲取URL,解析json格式數據

3、Cookies模擬登錄,Session保持會話

4、數據提取與清洗:用到正則re、BeautifulSoup及自定義的Tool類

5、用jieba.analyse編製程序,結巴分詞、詞頻統計,詞雲製作

6、基礎知識,range(start,end,n)、itertools.count(i)、remove等方法

陷阱與知識點的小雜燴

用來作為入門階段的複習總結倒是不錯的。本篇就是這樣啦~


推薦閱讀:

20170403Python控制流if、while、for語句學習
TensorFlow初步(3)
Python資料庫起航篇|零基礎起步
Sublime Text 3中怎麼更換python的版本?
詳解Python元類

TAG:Python | 爬虫 |