【python網絡編程】新浪爬蟲:關鍵詞搜索爬取微博數據


上學期參加了一個大數據比賽,需要抓取大量數據,於是我從新浪微博下手,本來准備使用新浪的API的,無奈新浪並沒有開放關鍵字搜索的API,所以只能用爬蟲來獲取了。幸運的是,新浪提供了一個高級搜索功能,為我們爬取數據提供了一個很好的切入點。

        在查閱了一些資料,參考了一些爬蟲的例子后,得到大體思路:構造URL,爬取網頁,然后解析網頁

        具體往下看~

        登陸新浪微博,進入高級搜索,如圖輸入,之后發送請求會發現地址欄變為如下:    http://s.weibo.com/weibo/%25E4%25B8%25AD%25E5%25B1%25B1%25E5%25A4%25A7%25E5%25AD%25A6&region=custom:44:1&typeall=1&suball=1&timescope=custom:2015-08-07-0:2015-08-08-0&Refer=g

       解析如下:
            固定地址部分:http://s.weibo.com/weibo/
            關鍵字二次UTF-8編碼:%25E4%25B8%25AD%25E5%25B1%25B1%25E5%25A4%25A7%25E5%25AD%25A6
            搜索地區:region=custom:44:1
            搜索時間范圍:timescope=custom:2015-08-07-0:2015-08-08-0
            可忽略項:Refer=g
            某次請求的頁數:page=1(第一頁可不加)

我們查看一下網頁源代碼看看有什么鬼:

 

    小伙伴們第一次看到肯定大呼我的天啊,真的是看的眼花繚亂。

    別着急,讓我娓娓道來。

    首先,我們定位到圖示的地方,即出現字符串<script>STK && STK.pageletM && STK.pageletM.view({"pid":"pl_weibo_direct"的地方,此處即搜索到的微博頁面的代碼啦~
    頁面是unicode碼,所以中文都不能正常顯示~而且上面沒有排版,才顯得如此雜亂。

    我們可以先對抓取到的頁面處理一下,這時就要用到lxml的etree了,它可以將網頁內容的結點構建成一棵樹。

    我們拿出其中一個結點出來看看:

<a class=\"W_texta W_fb\" nick-name=\"\u554a\u5be7\u5504\" href=\"http:\/\/weibo.com\/612364698\" target=\"_blank\" title=\"\u554a\u5be7\u5504\" usercard=\"id=1884932730&usercardkey=weibo_mp\"\t\tsuda-data=\"key=tblog_search_weibo&value=weibo_ss_1_name\" class=\"name_txt W_fb\">

    在這個結點中,我們可以獲取該條微博的博主的一些信息,如nick-name,微博地址href。

    我們再看看另一個結點:

<p class=\"comment_txt\" node-type=\"feed_list_content\" nick-name=\"\u554a\u5be7\u5504\">\u8fd9\u4e48\u52aa\u529b \u5c45\u7136\u5012\u6570\u7b2c\u4e94 \u5509 \u4e0d\u884c\u6211\u8981\u8ffd\u56de\u6765 \u8d8a\u632b\u8d8a\u52c7 \u4e0d\u53ef\u4ee5\u81ea\u66b4\u81ea\u5f03 \u4e0d\u53ef\u4ee5\u8ba9\u8d1f\u9762\u60c5\u7eea\u8dd1\u51fa\u6765 \u83dc\u575a\u5f3a \u52a0\u6cb9\u52a0\u6cb9\u52a0\u6cb9 \u6211\u8981\u4e0a<em class=\"red\">\u4e2d\u5c71\u5927\u5b66<\/em> \u6211\u8981\u548c\u5c0f\u54c8\u5427\u4e00\u6240\u5927\u5b66 \u62fc\u4e86<\/p>

    這個結點包含的數據即為微博的內容。

    這樣子就清晰很多了。至於如何搜索相應的結點,取得結點的屬性和內容等,我們用的是xpath這個工具。

    關於xpath,見文 http://blog.csdn.net/raptor/article/details/4516441

    獲得數據后,是數據的保存,我是將數據導入到excel中,用到的xlwt和xlrd這兩個模塊。

    最后數據的效果(我搜集的信息比較具體,需要訪問博主的個人主頁獲取,為便於大家閱讀、理解,下面代碼中刪去了這部分):

 

代碼:

 

[python]  view plain  copy
 
  1. # coding: utf-8  
  2.   
  3. ''''' 
  4. 以關鍵詞收集新浪微博 
  5. '''  
  6. import wx  
  7. import sys  
  8. import urllib  
  9. import urllib2  
  10. import re  
  11. import json  
  12. import hashlib  
  13. import os  
  14. import time  
  15. from datetime import datetime  
  16. from datetime import timedelta  
  17. import random  
  18. from lxml import etree  
  19. import logging  
  20. import xlwt  
  21. import xlrd  
  22. from xlutils.copy import copy  
  23.   
  24.   
  25. class CollectData():  
  26.     """數據收集類 
  27.        利用微博高級搜索功能,按關鍵字搜集一定時間范圍內的微博。 
  28.     """  
  29.     def __init__(self, keyword, startTime, interval='50', flag=True, begin_url_per = "http://s.weibo.com/weibo/"):  
  30.         self.begin_url_per = begin_url_per  #設置固定地址部分  
  31.         self.setKeyword(keyword)    #設置關鍵字  
  32.         self.setStartTimescope(startTime)   #設置搜索的開始時間  
  33.         #self.setRegion(region)  #設置搜索區域  
  34.         self.setInterval(interval)  #設置鄰近網頁請求之間的基礎時間間隔(注意:過於頻繁會被認為是機器人)  
  35.         self.setFlag(flag)    
  36.         self.logger = logging.getLogger('main.CollectData') #初始化日志  
  37.   
  38.     ##設置關鍵字  
  39.     ##關鍵字需解碼后編碼為utf-8  
  40.     def setKeyword(self, keyword):  
  41.         self.keyword = keyword.decode('GBK','ignore').encode("utf-8")  
  42.         print 'twice encode:',self.getKeyWord()  
  43.   
  44.     ##關鍵字需要進行兩次urlencode  
  45.     def getKeyWord(self):  
  46.         once = urllib.urlencode({"kw":self.keyword})[3:]  
  47.         return urllib.urlencode({"kw":once})[3:]          
  48.           
  49.     ##設置起始范圍,間隔為1天  
  50.     ##格式為:yyyy-mm-dd  
  51.     def setStartTimescope(self, startTime):  
  52.         if not (startTime == '-'):  
  53.             self.timescope = startTime + ":" + startTime  
  54.         else:  
  55.             self.timescope = '-'  
  56.   
  57.     ##設置搜索地區  
  58.     #def setRegion(self, region):  
  59.     #    self.region = region  
  60.   
  61.     ##設置鄰近網頁請求之間的基礎時間間隔  
  62.     def setInterval(self, interval):  
  63.         self.interval = int(interval)  
  64.   
  65.     ##設置是否被認為機器人的標志。若為False,需要進入頁面,手動輸入驗證碼  
  66.     def setFlag(self, flag):  
  67.         self.flag = flag  
  68.   
  69.     ##構建URL  
  70.     def getURL(self):  
  71.         return self.begin_url_per+self.getKeyWord()+"&typeall=1&suball=1×cope=custom:"+self.timescope+"&page="  
  72.   
  73.     ##爬取一次請求中的所有網頁,最多返回50頁  
  74.     def download(self, url, maxTryNum=4):  
  75.         hasMore = True  #某次請求可能少於50頁,設置標記,判斷是否還有下一頁  
  76.         isCaught = False    #某次請求被認為是機器人,設置標記,判斷是否被抓住。抓住后,需要,進入頁面,輸入驗證碼  
  77.         name_filter = set([])    #過濾重復的微博ID  
  78.           
  79.         i = 1   #記錄本次請求所返回的頁數  
  80.         while hasMore and i < 51 and (not isCaught):    #最多返回50頁,對每頁進行解析,並寫入結果文件  
  81.             source_url = url + str(i)   #構建某頁的URL  
  82.             data = ''   #存儲該頁的網頁數據  
  83.             goon = True #網絡中斷標記  
  84.             ##網絡不好的情況,試着嘗試請求三次  
  85.             for tryNum in range(maxTryNum):  
  86.                 try:  
  87.                     html = urllib2.urlopen(source_url, timeout=12)  
  88.                     data = html.read()  
  89.                     break  
  90.                 except:  
  91.                     if tryNum < (maxTryNum-1):  
  92.                         time.sleep(10)  
  93.                     else:  
  94.                         print 'Internet Connect Error!'  
  95.                         self.logger.error('Internet Connect Error!')  
  96.                         self.logger.info('url: ' + source_url)  
  97.                         self.logger.info('fileNum: ' + str(fileNum))  
  98.                         self.logger.info('page: ' + str(i))  
  99.                         self.flag = False  
  100.                         goon = False  
  101.                         break  
  102.             if goon:  
  103.                 lines = data.splitlines()  
  104.                 isCaught = True  
  105.                 for line in lines:  
  106.                     ## 判斷是否有微博內容,出現這一行,則說明沒有被認為是機器人  
  107.                     if line.startswith('<script>STK && STK.pageletM && STK.pageletM.view({"pid":"pl_weibo_direct"'):  
  108.                         isCaught = False  
  109.                         n = line.find('html":"')  
  110.                         if n > 0:  
  111.                             j = line[n + 7: -12].encode("utf-8").decode('unicode_escape').encode("utf-8").replace("\\", "")    #去掉所有的\  
  112.                             ## 沒有更多結果頁面  
  113.                             if (j.find('<div class="search_noresult">') > 0):  
  114.                                 hasMore = False  
  115.                             ## 有結果的頁面  
  116.                             else:  
  117.                                 #此處j要decode,因為上面j被encode成utf-8了  
  118.                                 page = etree.HTML(j.decode('utf-8'))  
  119.                                 ps = page.xpath("//p[@node-type='feed_list_content']")   #使用xpath解析得到微博內容  
  120.                                 addrs = page.xpath("//a[@class='W_texta W_fb']")   #使用xpath解析得到博主地址  
  121.                                 addri = 0  
  122.                                 #獲取昵稱和微博內容  
  123.                                 for p in ps:  
  124.                                     name = p.attrib.get('nick-name')    #獲取昵稱  
  125.                                     txt = p.xpath('string(.)')          #獲取微博內容  
  126.                                     addr = addrs[addri].attrib.get('href')  #獲取微博地址  
  127.                                     addri += 1  
  128.                                     if(name != 'None' and str(txt) != 'None' and name not in name_filter):  #導出數據到excel中  
  129.                                         name_filter.add(name)  
  130.                                         oldWb = xlrd.open_workbook('weiboData.xls', formatting_info=True)  
  131.                                         oldWs = oldWb.sheet_by_index(0)  
  132.                                         rows = int(oldWs.cell(0,0).value)  
  133.                                         newWb = copy(oldWb)  
  134.                                         newWs = newWb.get_sheet(0)  
  135.                                         newWs.write(rows, 0, str(rows))  
  136.                                         newWs.write(rows, 1, name)  
  137.                                         newWs.write(rows, 2, self.timescope)  
  138.                                         newWs.write(rows, 3, addr)  
  139.                                         newWs.write(rows, 4, txt)  
  140.                                         newWs.write(0, 0, str(rows+1))  
  141.                                         newWb.save('weiboData.xls')  
  142.                                         print "save with same name ok"  
  143.                         break  
  144.                 lines = None  
  145.                 ## 處理被認為是機器人的情況  
  146.                 if isCaught:  
  147.                     print 'Be Caught!'  
  148.                     self.logger.error('Be Caught Error!')  
  149.                     self.logger.info('filePath: ' + savedir)  
  150.                     self.logger.info('url: ' + source_url)  
  151.                     self.logger.info('fileNum: ' + str(fileNum))  
  152.                     self.logger.info('page:' + str(i))  
  153.                     data = None  
  154.                     self.flag = False  
  155.                     break  
  156.                 ## 沒有更多結果,結束該次請求,跳到下一個請求  
  157.                 if not hasMore:  
  158.                     print 'No More Results!'  
  159.                     if i == 1:  
  160.                         time.sleep(random.randint(3,8))  
  161.                     else:  
  162.                         time.sleep(10)  
  163.                     data = None  
  164.                     break  
  165.                 i += 1  
  166.                 ## 設置兩個鄰近URL請求之間的隨機休眠時間,防止Be Caught  
  167.                 sleeptime_one = random.randint(self.interval-25,self.interval-15)  
  168.                 sleeptime_two = random.randint(self.interval-15,self.interval)  
  169.                 if i%2 == 0:  
  170.                     sleeptime = sleeptime_two  
  171.                 else:  
  172.                     sleeptime = sleeptime_one  
  173.                 print 'sleeping ' + str(sleeptime) + ' seconds...'  
  174.                 time.sleep(sleeptime)  
  175.             else:  
  176.                 break  
  177.   
  178.     ##改變搜索的時間范圍,有利於獲取最多的數據     
  179.     def getTimescope(self, perTimescope):  
  180.         if not (perTimescope=='-'):  
  181.             times_list = perTimescope.split(':')  
  182.             start_date =  datetime(int(times_list[-1][0:4]),  int(times_list[-1][5:7]), int(times_list[-1][8:10]) )   
  183.             start_new_date = start_date + timedelta(days = 1)  
  184.             start_str = start_new_date.strftime("%Y-%m-%d")  
  185.             return start_str + ":" + start_str  
  186.         else:  
  187.             return '-'  
  188.   
  189. def main():  
  190.     logger = logging.getLogger('main')  
  191.     logFile = './collect.log'  
  192.     logger.setLevel(logging.DEBUG)  
  193.     filehandler = logging.FileHandler(logFile)  
  194.     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s')  
  195.     filehandler.setFormatter(formatter)  
  196.     logger.addHandler(filehandler)  
  197.   
  198.   
  199.     while True:  
  200.         ## 接受鍵盤輸入  
  201.         keyword = raw_input('Enter the keyword(type \'quit\' to exit ):')  
  202.         if keyword == 'quit':  
  203.             sys.exit()  
  204.         startTime = raw_input('Enter the start time(Format:YYYY-mm-dd):')  
  205.         #region = raw_input('Enter the region([BJ]11:1000,[SH]31:1000,[GZ]44:1,[CD]51:1):')  
  206.         interval = raw_input('Enter the time interval( >30 and deafult:50):')  
  207.   
  208.         ##實例化收集類,收集指定關鍵字和起始時間的微博  
  209.         cd = CollectData(keyword, startTime, interval)  
  210.         while cd.flag:  
  211.             print cd.timescope  
  212.             logger.info(cd.timescope)  
  213.             url = cd.getURL()  
  214.             cd.download(url)  
  215.             cd.timescope = cd.getTimescope(cd.timescope)  #改變搜索的時間,到下一天  
  216.         else:  
  217.             cd = None  
  218.             print '-----------------------------------------------------'  
  219.             print '-----------------------------------------------------'  
  220.     else:  
  221.         logger.removeHandler(filehandler)  
  222.         logger = None  
  223. ##if __name__ == '__main__':  
  224. ##    main()  

 

上面實現了數據的爬取,再結合上一篇文章中的模擬登錄,就可以美美的抓數據啦~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM