python网络编程新浪爬虫:关键词搜索爬取微博数据

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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()  

 

上面实现了数据的爬取,再结合上一篇文章中的模拟登录,就可以美美的抓数据啦~

以上是关于python网络编程新浪爬虫:关键词搜索爬取微博数据的主要内容,如果未能解决你的问题,请参考以下文章

Python爬虫开源项目代码,爬取微信淘宝豆瓣知乎新浪微博QQ去哪网等 代码整理

如何通过python调用新浪微博的API来爬取数据

爬取微博热搜top50

23个Python爬虫开源项目代码:爬取微信淘宝豆瓣知乎微博等

Python 超简单爬取新浪微博数据 (高级版)

合肥工业大学python大作业之爬虫(手把手教你爬取微博热搜)