1000家公司五年的新浪微博採集

本文原始github地址為:動態IP解決新浪的反爬蟲機制,快速抓取內容。

作者:@szcf-weiya

目錄:

  1. ## 項目背景

  2. ## 目標網頁

  3. ## 解析網頁

  4. ## 數據存儲

  5. ## 動態IP
  6. ## 運行環境

  7. ## 結果
  8. [TOC]

## 項目背景

以公司名稱為關鍵詞在[新浪微博](微博搜索 - 微博)中搜索2011-2015五年的所有微博,並統計評論數、轉發數、點贊數,需要搜索的公司有1000個左右。

在ADSL撥號上網的計算機上運行Python爬蟲程序,並將數據存儲到sqlite3資料庫中。項目主頁為[動態IP解決新浪的反爬蟲機制,快速抓取內容。](動態IP解決新浪的反爬蟲機制,快速抓取內容。),其中有完整的爬蟲代碼。

同步發布在 反反爬蟲技術倉庫 以及博客:

以公司為關鍵詞的新浪微博採集 | | URl-team

本文將詳細闡述具體爬蟲實現過程。

## 目標網頁

注意到,在未登錄狀態下是沒有高級搜索的,以「東方海洋」公司為例,我們只能得到如下圖所示的搜索結果![](search1.png)

而我們需要搜索的是特定時間段的微博,發現在登錄狀態是有高級搜索的,可以設置搜索時間等等,如下圖![](search2.png)

但搜索結果的顯示頁面數是有限的,也就是不管微博數目有多少,都只顯示特定頁數的微博。比如搜索「東方海洋」,如果選擇2011.01.01-2012.12.31為搜索區間,則限制頁數為38頁。

![](pages38.png)

如果選擇2011.01.01-2015.12.31為搜索區間,則限制頁數為35頁。![](pages35.png)

可見新浪搜索結果的顯示條數是有限制的,大致為40個頁面(一般每個頁面20條微博)。所以如果我們之間設置搜索區間為2011.01.01-2015.12.31,是得不到**所有**微博的。於是我選擇的搜索策略是,**每次搜索一天的微博**,這樣頁面數一般就只有1個頁面,就算多一點也不太可能達到限制頁面的數量,如果假設**未達到限制頁面數的搜索結果,其顯示的是所有微博**,則可以認為此時得到的搜索結果就是每天的所有微博。接著對每一天進行搜索就OK了。

但上面的設置搜索時間是在登錄狀態下實現的,考慮隱私等因素,還是避免使用登錄(模擬登錄是可以實現的,自己筆記本上運行做過試驗了)。於是想直接在非登錄態之間訪問上面設置時間後的網址,如直接在瀏覽器中輸入網址[http://s.weibo.com/weibo/%25E4%25B8%259C%25E6%2596%25B9%25E6%25B5%25B7%25E6%25B4%258B&typeall=1&suball=1×cope=custom:2011-01-01:2012-01-01&Refer=g](http://s.weibo.com/weibo/%25E4%25B8%259C%25E6%2596%25B9%25E6%25B5%25B7%25E6%25B4%258B&typeall=1&suball=1×cope=custom:2011-01-01:2012-01-01&Refer=g),竟然發現也是可行的,只不過一個缺點是此時只顯示1個頁面的搜索結果,對於一天的微博量,這已經足夠了(登錄狀態下試驗,基本上單天的搜索結果都只有一個頁面)。

於是,我們直接在非登錄態下進行抓取,這樣我們就可以構造需要抓取的目標網頁。

```python

import datetimenimport urllibnntm_start = datetime.date(2011,1,1)ntm_delta = datetime.timedelta(days=1)ntm_end = datetime.date(2015,12,31)nkeyword = 東方海洋nclass SinaCrawl():n def __init__(self,keyword,tm_start,tm_delta,conn,session):n self.url = 微博搜索 - 微博n self.URL = self.url + urllib.quote(keyword) + &typeall=1&suball=1&timescope=custom:+str(tm_start)+:+str(tm_start)+&Refer=gn self.myheader = {User-Agent:"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.2; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.40.1 Safari/534.6 TouchPad/1.0"}n self.conn = connn self.session = sessionn

```

## 解析網頁

利用瀏覽器的開發者工具,我們首先對網頁內容進行分析,找到我們需要抓取內容在源碼中的位置。![](f12.png)

然而這樣會有一個問題,本以為找到了所要抓取內容的位置,但是當使用BeautifulSoup尋找結點時,會發現找不到。原來我們獲取得到的網頁源碼並非如開發者工具中所示,而是如下圖所示![](sources.png)

注意到圖中的紅色方框,圖中藍色高亮部分經過一定的處理後是一個JSON,而在開發者工具中顯示的元素結構是JSON中html對應的值。於是我們首先需要提取該條script,注意到該條script區別於其他script的是含有"pid":"pl_weibo_direct",利用正則表達式便可以提出該條script。

```python

import requestsnndef getWeiboContent(self):n weiboContent = ""n try:n req = self.session.get(self.URL, headers = self.myheader)n if req.status_code == 200:n print This session work.n else:n print This session not work with code 200.n return Falsen except:n print This session not work.n return Falsen try:n page = req.contentnn except httplib.IncompleteRead:n print Incompleted!n return Falsen n soupPage = BeautifulSoup(page, lxml)n numList = soupPage.find_all(script)n if len(numList) == 0:n print you may need to input an access coden return Falsen for i in range(0, len(numList)):n IsSearch = re.search(r""pid":"pl_weibo_direct"", str(numList[i]))n if IsSearch == None:n continuen else:n weiboContent = str(numList[i])n breakn return weiboContentn

```

提取有意義的script後進行JSON解析,取出html的值得到「真正」的微博網頁源碼weiboHtml。

```python

def getWeiboHtml(self):n weiboContent = self.getWeiboContent()n if weiboContent == "":n print WeiboContents are empty. You may need to input an access code.n return Falsen elif weiboContent == False:n return Falsen n # in case some empty json elementn substr = re.compile("[]")n weiboContent = substr.sub(""NULL"", weiboContent)n n substr1 = re.compile("^.*STK.pageletM.view(")n substr2 = re.compile(")$")n substr4 = re.compile(r[)n substr5 = re.compile(r])n substr6 = re.compile(r)</script>$)n weiboContent = substr1.sub("", weiboContent)n weiboContent = substr2.sub("", weiboContent)n weiboContent = substr4.sub("", weiboContent)n weiboContent = substr5.sub("", weiboContent)n weiboContent = substr6.sub("", weiboContent)n try:n weiboJson = json.loads(weiboContent)n except:n print Json Error!n return -3n if weiboJson == None:n print you may need to input an access coden return Truen weiboHtml = weiboJson["html"]n return weiboHtmln

```

然後我們分析weiboHtml,確定所要抓取信息的位置,利用BeautifulSoup提取相應的數據。

首先判斷是否無搜索結果

```python

soup = BeautifulSoup(weiboHtml, lxml)nNoresult = soup.find_all(div,{class:pl_noresult})nif len(Noresult) != 0:n print NO result for the current search criteria.n # No resultn return 0n

```

若有搜索結果返回當前頁面的每條微博

```python

WeiboItemAll = soup.find_all(div,{action-type:feed_list_item})nWeiboItemFeed = soup.find_all(ul, {class:feed_action_info feed_action_row4})nWeiboItemContent = soup.find_all(p,{class:comment_txt})n

```

WeiboItemAll 為整條微博,它包括WeiboItemContent和WeiboItemFeed ![](WeiboItemAll.png)

然後將每一條微博轉換為soup對象,提取微博內容、點贊數、評論數以及轉發數。

```python

for i in range(0, len(WeiboItemContent)):n soupContent = BeautifulSoup(str(WeiboItemContent[i]), "lxml")n soupAll = BeautifulSoup(str(WeiboItemAll[i]), "lxml")n soupFeed = BeautifulSoup(str(WeiboItemFeed[i]), "lxml")nn mid = str(soupAll.div.attrs[mid])n STR = ""n for string in soupContent.strings:n STR += stringn content = STRn when = soupAll.find(a,{class:W_textb})n weibotime = str(when[title])n WeiboItemPraised = soupFeed.ul.contents[7]n WeiboItemComment = soupFeed.ul.contents[5]n WeiboItemForward = soupFeed.ul.contents[3]n praisedString = WeiboItemPraised.contents[0].contents[0].contents[1].stringn commentContent = WeiboItemComment.contents[0].contents[0].contentsn forwardString = WeiboItemForward.contents[0].contents[0].contents[1].stringn n if praisedString == None:n tpraised = 0n else:n praised = int(str(praisedString))n if len(commentContent) == 1:n comment = 0n else:n comment = int(str(commentContent[1].string))nn if forwardString == None:n forward = 0n else:n forward = int(str(forwardString))n

```

## 數據存儲

採用sqlite3資料庫進行存儲數據

```python

import sqlite3ndef CreateWeiboTable(db_name):n conn = sqlite3.connect(db_name)n try:n create_tb_sql = n CREATE TABLE IF NOT EXISTS weibon (mid TEXT,n content TEXT,n time TEXT,n praised INTEGER,n comment INTEGER,n forward INTEGER);n n conn.execute(create_tb_sql)n except:n print CREATE table failedn return Falsen conn.commit()n conn.close()ntndbname = test.dbnCreateWeiboTable(dbname)nconn = sqlite3.connect(dbname)n# 抓取得到的微博數據nweiboitem = (mid, content, weibotime, praised, comment, forward)nconn.execute("INSERT INTO weibo VALUES(?,?,?,?,?,?)", weiboitem)nconn.commit()n

```

至此,基本的爬蟲代碼已經完成,但是實際運行會出現一個很嚴重的問題,便是會在抓取過程中出現驗證碼。

## 動態IP

因為我們需要不斷進行抓取,而抓取次數過高新浪會有IP限制,所以便需要輸入驗證碼(如下)。![](yzm.png)

經試驗,一般連續搜索20次就會要求輸入驗證碼。解決驗證碼問題是本項目的重點,也是最關鍵的一步,如果解決了,那按照上面的爬蟲進行就好了。為了解決驗證碼問題,進行了很多嘗試。

#### 識別驗證碼

太天真了,竟然想去識別。這裡嘗試的是pytesser庫,使用其實是很簡單的。

```python

from pytesser import *nimgfile = "test.png" # 當前路徑中的一個test.png驗證碼圖象nprint image_file_to_string(imgfile)n

```

如果是下面這種簡單的驗證碼,這種方式的正確率還是蠻高的。![](simpleyzm.jpg)

另外,也嘗試了PIL庫先對驗證碼進行預處理(均值濾波、中值濾波等等),但效果均不理想,故放棄。

#### 代理IP

網上也有相關的案例,但經試驗好多代理IP無法使用,或許也因為沒有採用正確的處理方式。

#### 人工輸入驗證碼

迫不得已的方法,竟然花了2個小時完成了一個公司的搜索,但之後也沒有繼續這樣傻傻地人工輸入了,權當是嘗試了各種方案都不行的放鬆吧。

#### ADSL撥號上網動態IP

最後採用的方案。首先得有一個ADSL撥號上網的計算機,可以在淘寶上購買ADSL撥號上網的winxp伺服器,得到寬頻上網賬號USERNAME及其密碼PASSWORD,設置寬頻名稱為NAME,則可以通過下列Python代碼斷開網路連接與撥號重連

```python

os.popen(rasdial /disconnect).read()nos.popen(rasdial NAME USERNAME PASSWORD).read()n

```

實際操作中,設置抓取15次(經試驗,新浪微博一般抓取20次左右要求輸入驗證碼)後斷開網路連接,然後重連,實現更換IP的效果,也就避免了輸出驗證碼。

## 運行環境

- Python2.7

- winxp伺服器(通過某寶購買,關鍵是ADSL撥號功能,不然無法實現動態IP,也就解決不了新浪的反爬蟲機制)

## 結果

- 每個公司五年內的微博(通過sqlite3存儲)存到一個對應的.db文件中,下面截圖為company0000.db的微博。 ![](db.png)

- 所有公司微博評論數、轉發數、點贊數的統計(直接使用sql查詢語句進行統計)

比如統計每年總微博數

```python

time_sql = [(2011-01-01 00:00, 2011-12-31 23:59),n (2012-01-01 00:00, 2012-12-31 23:59),n (2013-01-01 00:00, 2013-12-31 23:59),n (2014-01-01 00:00, 2014-12-31 23:59),n (2015-01-01 00:00, 2015-12-31 23:59)]nnnum_total_sql = "select count(*) from weibo where datetime(time) >= datetime(%s) and datetime(time) <=datetime(%s);" %(time_sql[i][0], time_sql[i][1])nnres = str(conn.execute(num_total_sql).fetchone()[0]n

```

最後將結果寫進excel文件中,部分結果如下圖所示 ![](results.png)


推薦閱讀:

Python爬取新浪博客
Python數據分析及可視化實例之爬蟲源碼(02)

TAG:Python | 爬虫 | 新浪微博 |