爬取豆瓣有關張國榮日記(二)—— 策略源碼知識點
本來想用Scrapy來爬的,結果連續被ban。
設置動態UA、加Cookies、用vpn也無濟於事,輾轉一天多,累覺不愛。反爬機制不要太強啊,給豆瓣小組點個贊,服!!
不過,最後還是用一般方法的解決了
說來也奇怪,大概因為Scrapy是非同步多線程,所以容易被發現吧。
一、目標
爬取豆瓣所有關於張國榮的日記
1、獲取每一篇標題、作者、鏈接、點贊數、發布時間,將數據存入excel2、獲取所有日記內容,存入txt3、將所有文章匯總,jieba分詞,做成詞雲圖二、過程
1、分析網頁及源碼
豆瓣首頁搜索框中輸入 張國榮
順帶分析下其它
第二頁,start=20第三頁,start=402、具體步驟及問題解決
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端和移動端,移動端是http://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元類