因為要參加大學生的創新大賽,研究微博的博文所表達的情感,需要大量的微博的博文,而且無論是國內的某個學位,csdn,還是國外的Google,gayhub,codeproject,都找不到想要的程序,只好自己寫程序了。
贊美詩我在《攀登聯盟》裏找到壹個類似的程序,不過是在windows下,源碼是關閉的。而且爬取保存的文件,用notepad++打開,出現了很多奇怪的問題,我就放棄了。
0x001。基礎知識
這個程序是用python寫的,所以基本的Python知識是必須的。另外,如果妳有壹定的計算機網絡基礎,在前期準備的時候會少走很多彎路。
對於爬行動物,妳需要明確以下幾點:
1.爬取對象的分類可以分為以下幾類:第壹類是不需要登錄的,比如博主以前練手的時候爬的中國天氣網。這類網頁很難抓取,建議爬蟲新手爬這類網頁;二是登錄,如豆瓣、新浪微博,難爬;第三種獨立於前兩種,妳想要的信息壹般是動態刷新的,比如AJAX或者嵌入式資源。這種爬蟲是最難的,博主也沒研究過,這裏就不細說了(據同學說,淘寶的商品評論就屬於這壹類)。
2.如果同壹個數據源有多種形式(如電腦版、手機版、客戶端等。),更“純粹”的呈現方式更受青睞。比如新浪微博有網頁版和手機版,手機版可以通過電腦瀏覽器訪問。這個時候,我更喜歡手機版的新浪微博。
3.爬蟲壹般是把網頁下載到本地,然後通過某種方式提取感興趣的信息。換句話說,抓取網頁只是完成了壹半,妳需要從下載的html文件中提取妳感興趣的信息。這時候就需要壹些xml的知識了。在這個項目中,博客作者使用XPath提取信息,也可以使用XQuery等其他技術。詳情請訪問w3cschool。
4.爬蟲要盡可能模仿人類。現在網站的反抓取機制已經開發出來了。從驗證碼到IP禁止,爬蟲技術和反爬蟲技術可謂是連續博弈。
0x02。去
確定爬蟲的目標後,首先要訪問目標網頁,了解目標網頁屬於以上哪種爬蟲。另外,記錄下妳需要采取的步驟,才能得到妳感興趣的信息,比如是否需要登錄,如果需要登錄,是否需要驗證碼;妳需要做什麽才能得到妳想要的信息,是否需要提交壹些表格;妳想要的信息所在頁面的url有什麽規則等等。
以下博文以blogger項目為例。該項目抓取特定新浪微博用戶自註冊以來的所有微博博文,按關鍵詞抓取100頁微博博文(約1000篇)。
0x03。收集必要的信息
首先訪問目標網頁,發現需要登錄。進入登錄頁面如下:新浪微博手機版登錄頁面。
註意,在url的後半部分有許多像“%xx”這樣的轉義字符,這將在本文後面討論。
從這個頁面可以看到,登錄新浪微博手機版需要填寫賬號、密碼和驗證碼。
這個驗證碼只需要最近提供(本文創建於2016.3.11)。如果不需要提供驗證碼,會有兩種登錄方式。
第壹種方法是進行js模擬,填寫賬號密碼後點擊“登錄”按鈕。博主之前用這個方法寫了壹個Java爬蟲,現在找不到項目了,這裏就不贅述了。
第二種需要壹定的HTTP基礎,提交包含所需信息的HTTP POST請求。我們需要Wireshark工具來捕獲我們在登錄微博時發送和接收的數據包。如下圖所示,我抓取了登錄時收發的數據包。Wireshark搶到了1的結果。
在搜索欄中提供搜索條件"/(displayID)?page=(pagenum)" .這將是我們的爬蟲拼接url的基礎。
接下來看網頁的源代碼,找到我們想要的信息的位置。打開瀏覽器開發者工具,直接定位壹個微博,就可以找到它的位置,如下圖。
xpath
觀察html代碼,發現所有微博都在< div & gt標簽,這個標簽中有兩個屬性,其中class屬性是“c”和壹個唯壹的id屬性值。獲取這些信息有助於提取所需的信息。
此外,還有壹些因素需要特別註意。
*微博分為原創微博和轉發微博。
*根據發布時間與當前時間的不同,頁面上顯示時間的方式有多種,如“MM分鐘前”、“今天的HH:MM”、“MM月dd日HH:MM-DD hh: mm: SS”。*手機版新浪微博壹頁顯示約10條微博,註意總量* *。
0x04。編碼
1.抓取用戶微博
這個項目的開發語言是Python 2.7,項目中使用了壹些第三方庫,可以通過pip添加。
由於驗證碼阻擋了自動登錄的思路,用戶要訪問特定用戶的微博頁面,只能提供cookies。
第壹個是Python的請求模塊,它提供帶有cookies的url請求。
導入請求
打印請求。get (url,cookies = cookies)。內容使用此代碼打印帶有cookies的URL請求頁面結果。
首先,獲取用戶的微博頁面數量。通過檢查網頁的源代碼,找到代表頁數的元素,通過XPath等技術提取頁數。
頁數
該項目使用lxml模塊通過XPath提取html。
首先,導入lxml模塊,項目中只使用etree,所以從lxml導入etree。
然後用下面的方法返回頁碼。
def getpagenum(self):
URL = self . geturl(pagenum = 1)
html = requests.get(url,cookies=self.cook)。內容#訪問第壹頁獲取頁碼。
選擇器= etree。HTML(html)
pagenum = selector . XPath('//input[@ name = " MP "]/@ value ')[0]
return int(pagenum)
下壹步是連續拼接網址->訪問網址-& gt;下載網頁。
需要註意的是,由於新浪反爬取機制的存在,如果同壹個cookies訪問頁面過於頻繁,就會進入類似的“冷靜期”,即會返回壹個無用的頁面。通過分析這個無用的頁面,發現這個頁面在特定的地方會有特定的信息,這個頁面對我們是否有用可以通過XPath技術來判斷。
def ispageneeded(html):
選擇器= etree。HTML(html)
嘗試:
title = selector . XPath('//title ')[0]
除了:
返回False
返回title.text!= '微博廣場'和title.text!= '微博'
如果有無用的頁面,妳只需要再次訪問它們。但是通過後來的實驗發現,如果長時間頻繁訪問它們,那麽返回的頁面都是無用的,程序會陷入死循環。為了防止程序陷入死循環,博主設置了壹個trycount閾值,超過閾值後方法會自動返回。
下面的代碼片段展示了單線程爬蟲的方法。
def startcrawling(self,startpage=1,trycount=20):
嘗試= 0
嘗試:
OS . mkdir(sys . path[0]+'/Weibo _ raw/'+self . wanted)除了例外,e:
打印字符串(e)
isdone = False
while not isdone並嘗試& lt嘗試計數:
嘗試:
pagenum = self.getpagenum()
isdone = True
除了例外,e:
嘗試+= 1
if attempt == trycount:
返回False
i =起始頁
而我& lt= pagenum:
嘗試= 0
isneeded = False
html = ' '
while not isneeded and attempt & lt嘗試計數:
html = self . getpage(self . geturl(I))
isneeded = self . ispageneeded(html)
如果不需要:
嘗試+= 1
if attempt == trycount:
返回False
self . save html(sys . path[0]+'/Weibo _ raw/'+self . wanted+'/'+str(I)+'。txt ',html)打印字符串(i) + '/' +字符串(pagenum - 1)
i += 1
返回True
考慮到程序的時間效率,在寫了單線程爬蟲之後,博主又寫了多線程爬蟲版本。基本思路是微博頁面數除以跟帖數。比如微博中的壹個用戶有100個微博頁面,程序有10個線程,那麽每個線程只負責爬取10個頁面。其他基本思路和單線程差不多,只有邊界值需要小心處理,這裏就不贅述了。另外,由於多線程的效率比較高,並發量特別大,服務器很容易返回無效頁面,所以trycount的設置比較重要。博主在寫這條微博的時候,用了壹個新的cookie來測試誰爬了北京郵電大學的微博。3976條微博文章全部成功爬取,博文提取。只用了15s,這其實可能和新舊cookies以及網絡環境有關。命令行設置如下,項目網站中解釋了命令行的含義:python main.py _ T _ WM = xxxSUHB = xxxSUB = xxxGSID _ CTANDWM = XXX UBUPPT M 20 20以上爬行工作的基本介紹結束,接下來分析爬蟲的第二部分。因為項目提供了多線程抓取的方法,而多線程壹般是亂序的,但是微博的博文是按時間排序的,所以項目采用了折中的方法,將下載的頁面保存在本地文件系統中,每個頁面以其頁碼作為文件名。爬行工作完成後,遍歷並解析文件夾中的所有文件。
通過前面的觀察,我們了解到了微博的博文都有哪些特點。通過使用XPath技術,從這個頁面中提取所有具有這個特性的標簽並不困難。
再次,微博分為轉發微博和原創微博,時間表達。另外,因為我們的研究課題只對微博文字感興趣,所以不考慮插圖。
def startparsing(self,parsing time = datetime . datetime . now()):
basepath = sys . path[0]+'/Weibo _ raw/'+self . uid for filename in OS . listdir(basepath):
if filename.startswith(' . '):
繼續
path = basepath + '/' + filename
f =打開(路徑,“r”)
html = f.read()
選擇器= etree。HTML(html)
weiboitems = selector . XPath('//div[@ class = " c "][@ id]')用於Weibo items中的項目:
微博=微博()
weibo.id = item.xpath('。/@id')[0]
cmt = item.xpath('。/div/span[@ class = " CMT "]')if len(CMT)!= 0:
weibo.isrepost = True
weibo.content = cmt[0]。文本
否則:
weibo.isrepost = False
ctt = item.xpath('。/div/span[@class="ctt"]')[0]
如果ctt.text不為None:
weibo.content += ctt.text
對於ctt.xpath中的。/a '):
如果a.text不是None:
微博.內容+= a.text
如果a.tail不為None:
weibo.content += a.tail
if len(cmt)!= 0:
reason = CMT[1]. text . split(u ' \ xa0 ')
if len(原因)!= 1:
Weibo . reportstroy = reason[0]
ct = item.xpath('。/div/span[@class="ct"]')[0]
time = ct.text.split(u'\xa0')[0]
weibo.time = self.gettime(self,time,parsingtime)self.weibos.append(微博。__字典_ _)
f.close()
方法傳遞的參數parsingtime的設置初衷是開發初期抓取和解析可能不會同時進行(並非嚴格意義上的“同時”),微博時間顯示以訪問時間為準,比如抓取時間為10:00,五分鐘前發布了壹條微博顯示,但如果解析時間為10:30,解析時間就會出錯,所以,到爬蟲基本發育結束,爬行和解析的開始時間差距會縮小,時間差就是爬行過程的時間,基本可以忽略。
解析結果保存在列表中。最後,列表以json格式保存到文件系統,並刪除轉換文件夾。
定義保存(自己):
f = open(sys . path[0]+'/Weibo _ parsed/'+self . uid+'。txt ',' w ')JSON str = JSON . dumps(self . weibos,indent=4,確保_ascii=False)f.write(jsonstr)
f.close()
抓取關鍵詞
同樣,收集必要的信息。在微博手機搜索頁面輸入“python”,觀察網址,研究其規律。雖然第壹頁沒有規則,但是我們在第二頁發現了壹個規則,這個規則可以應用回第壹頁。
第二頁
申請後的第壹頁
觀察url可以發現,url中唯壹的變量是關鍵字和頁面(其實hideSearchFrame對我們的搜索結果和爬蟲沒有影響),所以我們可以在代碼中控制這兩個變量。
另外,如果關鍵詞是中文,那麽網址需要轉換漢字。例如,如果我們在搜索框中鍵入“Happy”進行搜索,我們發現url顯示Happy Search如下。
但是它被復制為
/search/mblog?hideSearchFrame = & amp關鍵字= % E5 % BC % 80 % E5 % BF % 83 & ampPage=1好在python的urllib庫有qoute方法處理中文轉換的功能(如果是英文就不轉換了),所以在拼接URL之前用這個方法處理參數。
另外,考慮到關鍵詞搜索屬於數據收集階段使用的方法,我們這裏只提供網頁的單線程下載。如果有多線程的需求,可以按照多線程抓取用戶微博的方法自己重寫。最後提取並保存下載的網頁(我知道這個模塊設計有點奇怪,所以打算重新創建的時候再改(郝),就這樣吧)。
def關鍵字爬網(self,keyword):
real keyword = URL lib . quote(keyword)#用中文處理關鍵字。
嘗試:
OS . mkdir(sys . path[0]+'/keywords ')
除了例外,e:
打印字符串(e)
微博= []
嘗試:
high points = re . compile(u '[\ u 00010000-\ u 0010 ffff]')#處理表情符號,但是好像不管用。
除了re.error:
high points = re . compile(u '[\ ud 800-\ uDBFF][\ UDC 00-\ uDFFF]')pagenum = 0
isneeded = False
當不需要時:
html = self . get page('/search/mblog?關鍵字= % s & amppage = 1 ' % real keyword)is needed = self . ispageneeded(html)
如果需要:
選擇器= etree。HTML(html)
嘗試:
pagenum = int(selector . XPath('//input[@ name = " MP "]/@ value ')[0])除了:
pagenum = 1
對於範圍內的I(1,pagenum + 1):
嘗試:
isneeded = False
當不需要時:
html = self . get page('/search/mblog?關鍵字= % s & amppage=%s' % (realkeyword,str(I)))is needed = self . ispageneeded(html)
選擇器= etree。HTML(html)
weiboitems = selector . XPath('//div[@ class = " c "][@ id]')用於Weibo items中的項目:
cmt = item.xpath('。/div/span[@ class = " CMT "]')if(len(CMT))= = 0:
ctt = item.xpath('。/div/span[@class="ctt"]')[0]
如果ctt.text不為None:
text = etree.tostring(ctt,method='text ',encoding = " unicode ")tail = CTT . tail
if text.endswith(tail):
index = -len(tail)
text = text[1:index]
text = highpoints.sub(u'\u25FD ',text) #表情符號的處理方式,似乎行不通。
微博文本=文本
微博附加(微博文本)
打印字符串(i) + '/' +字符串(pagenum)
除了例外,e:
打印字符串(e)
f = open(sys . path[0]+'/keywords/'+keyword+'。txt ',' w ')嘗試:
f.write(json.dumps(weibos,indent=4,確保_ ascii = False))Exception除外,例如:
打印字符串(ex)
最後:
f.close()
博客作者以前從來沒有寫過任何爬蟲程序。為了獲取新浪微博博文,博主們編寫了三種不同的爬蟲程序,包括Python和Java。爬行動物不能用很正常。不要氣餒。爬蟲程序和反爬行機制壹直在不斷博弈。
另外,轉載請告知博主,如果妳認為博是老板就不需要告知。