使用网页爬虫(高级搜索功能)搜集含关键词新浪微博数据

作为国内社交媒体的领航者,很遗憾,新浪微博没有提供以“关键字+时间+区域”方式获取的官方API。当我们看到国外科研成果都是基于某关键字获得的社交媒体数据,心中不免凉了一大截,或者转战推特。再次建议微博能更开放些!


1、切入点

庆幸的是,新浪提供了高级搜索功能。找不到?这个功能需要用户登录才能使用……没关系,下面将详细讲述如何在无须登录的情况下,获取“关键字+时间+区域”的新浪微博。

首先我们还是要登录一下,看看到底是个什么样的功能。


然后我们看看地址栏:

[html]  view plain  copy
  1. http://s.weibo.com/wb/%25E4%25B8%25AD%25E5%259B%25BD%25E5%25A5%25BD%25E5%25A3%25B0%25E9%259F%25B3&xsort=time&region=custom:11:1000&timescope=custom:2014-07-09-2:2014-07-19-4&Refer=g  


这么长?其实蛮清晰、简单的。解析如下:


固定地址部分:http://s.weibo.com/wb/

关键字(2次URLEncode编码):%25E4%25B8%25AD%25E5%259B%25BD%25E5%25A5%25BD%25E5%25A3%25B0%25E9%259F%25B3

返回微博的排序方式(此处为“实时”):xsort=time

搜索地区:region=custom:11:1000

搜索时间范围:timescope=custom:2013-07-02-2:2013-07-09-2

可忽略项:Refer=g

是否显示类似微博(未出现):nodup=1    注:加上这个选项可多收集微博,建议加上。默认为省略参数,即省略部分相似微博。

某次请求的页数(未出现):page=1


既然是这么回事,我们接下来就可以使用网页爬虫的方式获取“关键字+时间+区域”的微博了……

2、采集思路

大体思路如下:构造URL,爬取网页,然后解析网页中的微博信息,如下图所示。微博官方提供了根据微博ID进行查询的微博信息的API,故本文只负责讲述收集微博ID。

另外,高级搜索最多返回50页微博,那么时间间隔设置最小为宜。所以时间范围(timescope)可设置为1小时,如2013-07-01-2:2013-07-01-2。

目前没有模拟登陆,所以需要设置两个邻近URL请求之间的随机休眠时间,过于频繁会被认为是机器人,你懂的。



3、具体实现

作为爬虫小工具,用python非常适合。作为python初学者,不要怪我写得像java。首先实现一个爬取每个小时的类。

[python]  view plain  copy
  1. class CollectData():  
  2.     """每小时数据收集类 
  3.         利用微博高级搜索功能,按关键字搜集一定时间范围内的微博。 
  4.  
  5.         大体思路:构造URL,爬取网页,然后解析网页中的微博ID。后续利用微博API进行数据入库。本程序只负责收集微博的ID。 
  6.  
  7.         登陆新浪微博,进入高级搜索,输入关键字”空气污染“,选择”实时“,时间为”2013-07-02-2:2013-07-09-2“,地区为”北京“,之后发送请求会发现地址栏变为如下: 
  8.         http://s.weibo.com/wb/%25E7%25A9%25BA%25E6%25B0%2594%25E6%25B1%25A1%25E6%259F%2593&xsort=time&region=custom:11:1000×cope=custom:2013-07-02-2:2013-07-09-2&Refer=g 
  9.  
  10.             固定地址部分:http://s.weibo.com/wb/ 
  11.             关键字二次UTF-8编码:%25E7%25A9%25BA%25E6%25B0%2594%25E6%25B1%25A1%25E6%259F%2593 
  12.             排序为“实时”:xsort=time 
  13.             搜索地区:region=custom:11:1000 
  14.             搜索时间范围:timescope=custom:2013-07-02-2:2013-07-09-2 
  15.             可忽略项:Refer=g 
  16.             显示类似微博:nodup=1    注:这个选项可多收集微博,建议加上。默认不加此参数,省略了部分相似微博。 
  17.             某次请求的页数:page=1 
  18.  
  19.         另外,高级搜索最多返回50页微博,那么时间间隔设置最小为宜。所以该类设置为搜集一定时间段内最多50页微博。 
  20.     """  
  21.     def __init__(self, keyword, startTime, region, savedir, interval='50', flag=True, begin_url_per = "http://s.weibo.com/weibo/"):  
  22.         self.begin_url_per = begin_url_per  #设置固定地址部分,默认为"http://s.weibo.com/weibo/",或者"http://s.weibo.com/wb/"  
  23.         self.setKeyword(keyword)    #设置关键字  
  24.         self.setStartTimescope(startTime)   #设置搜索的开始时间  
  25.         self.setRegion(region)  #设置搜索区域  
  26.         self.setSave_dir(savedir)   #设置结果的存储目录  
  27.         self.setInterval(interval)  #设置邻近网页请求之间的基础时间间隔(注意:过于频繁会被认为是机器人)  
  28.         self.setFlag(flag)  #设置  
  29.         self.logger = logging.getLogger('main.CollectData'#初始化日志  
  30.   
  31.     ##设置关键字  
  32.     ##关键字需解码  
  33.     def setKeyword(self, keyword):  
  34.         self.keyword = keyword.decode('GBK').encode("utf-8")  
  35.         print 'twice encode:',self.getKeyWord()  
  36.   
  37.     ##设置起始范围,间隔为1小时  
  38.     ##格式为:yyyy-mm-dd-HH  
  39.     def setStartTimescope(self, startTime):  
  40.         if not (startTime == '-'):  
  41.             self.timescope = startTime + ":" + startTime  
  42.         else:  
  43.             self.timescope = '-'  
  44.   
  45.     ##设置搜索地区  
  46.     def setRegion(self, region):  
  47.         self.region = region  
  48.   
  49.     ##设置结果的存储目录  
  50.     def setSave_dir(self, save_dir):  
  51.         self.save_dir = save_dir  
  52.         if not os.path.exists(self.save_dir):  
  53.             os.mkdir(self.save_dir)  
  54.   
  55.     ##设置邻近网页请求之间的基础时间间隔  
  56.     def setInterval(self, interval):  
  57.         self.interval = int(interval)  
  58.   
  59.     ##设置是否被认为机器人的标志。若为False,需要进入页面,手动输入验证码  
  60.     def setFlag(self, flag):  
  61.         self.flag = flag  
  62.   
  63.     ##构建URL  
  64.     def getURL(self):  
  65.         return self.begin_url_per+self.getKeyWord()+"&region=custom:"+self.region+"&xsort=time×cope=custom:"+self.timescope+"&nodup=1&page="  
  66.   
  67.     ##关键字需要进行两次urlencode  
  68.     def getKeyWord(self):  
  69.         once = urllib.urlencode({"kw":self.keyword})[3:]  
  70.         return urllib.urlencode({"kw":once})[3:]  
  71.   
  72.     ##爬取一次请求中的所有网页,最多返回50页  
  73.     def download(self, url, maxTryNum=4):  
  74.         content = open(self.save_dir + os.sep + "weibo_ids.txt""ab")  #向结果文件中写微博ID  
  75.   
  76.         hasMore = True  #某次请求可能少于50页,设置标记,判断是否还有下一页  
  77.         isCaught = False    #某次请求被认为是机器人,设置标记,判断是否被抓住。抓住后,需要复制log中的文件,进入页面,输入验证码  
  78.         mid_filter = set([])    #过滤重复的微博ID  
  79.           
  80.         i = 1   #记录本次请求所返回的页数  
  81.         while hasMore and i < 51 and (not isCaught):    #最多返回50页,对每页进行解析,并写入结果文件  
  82.             source_url = url + str(i)   #构建某页的URL  
  83.             data = ''   #存储该页的网页数据  
  84.             goon = True #网络中断标记  
  85.   
  86.             ##网络不好的情况,试着尝试请求三次  
  87.             for tryNum in range(maxTryNum):  
  88.                 try:  
  89.                     html = urllib2.urlopen(source_url, timeout=12)  
  90.                     data = html.read()  
  91.                     break  
  92.                 except:  
  93.                     if tryNum < (maxTryNum-1):  
  94.                         time.sleep(10)  
  95.                     else:  
  96.                         print 'Internet Connect Error!'  
  97.                         self.logger.error('Internet Connect Error!')  
  98.                         self.logger.info('filePath: ' + savedir)  
  99.                         self.logger.info('url: ' + source_url)  
  100.                         self.logger.info('fileNum: ' + str(fileNum))  
  101.                         self.logger.info('page: ' + str(i))  
  102.                         self.flag = False  
  103.                         goon = False  
  104.                         break  
  105.             if goon:  
  106.                 lines = data.splitlines()  
  107.                 isCaught = True  
  108.                 for line in lines:  
  109.                     ## 判断是否有微博内容,出现这一行,则说明没有被认为是机器人  
  110.                     if line.startswith('<script>STK && STK.pageletM && STK.pageletM.view({"pid":"pl_weibo_direct"'):  
  111.                         isCaught = False  
  112.                         n = line.find('html":"')  
  113.                         if n > 0:  
  114.                             j = line[n + 7: -12].encode("utf-8").decode('unicode_escape').encode("utf-8").replace("\\", "")  
  115.                             ## 没有更多结果页面  
  116.                             if (j.find('<div class="search_noresult">') > 0):  
  117.                                 hasMore = False  
  118.                             ## 有结果的页面  
  119.                             else:  
  120.                                 page = etree.HTML(j)  
  121.                                 dls = page.xpath(u"//dl")   #使用xpath解析  
  122.                                 for dl in dls:  
  123.                                     mid = str(dl.attrib.get('mid'))  
  124.                                     if(mid != 'None' and mid not in mid_filter):  
  125.                                         mid_filter.add(mid)  
  126.                                         content.write(mid)  
  127.                                         content.write('\n')  
  128.                         break  
  129.                 lines = None  
  130.                 ## 处理被认为是机器人的情况  
  131.                 if isCaught:  
  132.                     print 'Be Caught!'  
  133.                     self.logger.error('Be Caught Error!')  
  134.                     self.logger.info('filePath: ' + savedir)  
  135.                     self.logger.info('url: ' + source_url)  
  136.                     self.logger.info('fileNum: ' + str(fileNum))  
  137.                     self.logger.info('page:' + str(i))  
  138.                     data = None  
  139.                     self.flag = False  
  140.                     break  
  141.                 ## 没有更多结果,结束该次请求,跳到下一个请求  
  142.                 if not hasMore:  
  143.                     print 'No More Results!'  
  144.                     if i == 1:  
  145.                         time.sleep(random.randint(55,75))  
  146.                     else:  
  147.                         time.sleep(15)  
  148.                     data = None  
  149.                     break  
  150.                 i += 1  
  151.                 ## 设置两个邻近URL请求之间的随机休眠时间,你懂的。目前没有模拟登陆  
  152.                 sleeptime_one = random.randint(self.interval-30,self.interval-10)  
  153.                 sleeptime_two = random.randint(self.interval+10,self.interval+30)  
  154.                 if i%2 == 0:  
  155.                     sleeptime = sleeptime_two  
  156.                 else:  
  157.                     sleeptime = sleeptime_one  
  158.                 print 'sleeping ' + str(sleeptime) + ' seconds...'  
  159.                 time.sleep(sleeptime)  
  160.             else:  
  161.                 break  
  162.         content.close()  
  163.         content = None  
  164.   
  165.     ##改变搜索的时间范围,有利于获取最多的数据     
  166.     def getTimescope(self, perTimescope, hours):  
  167.         if not (perTimescope=='-'):  
  168.             times_list = perTimescope.split(':')  
  169.             start_datetime = datetime.datetime.fromtimestamp(time.mktime(time.strptime(times_list[-1],"%Y-%m-%d-%H")))  
  170.             start_new_datetime = start_datetime + datetime.timedelta(seconds = 3600)  
  171.             end_new_datetime = start_new_datetime + datetime.timedelta(seconds = 3600*(hours-1))  
  172.             start_str = start_new_datetime.strftime("%Y-%m-%d-%H")  
  173.             end_str = end_new_datetime.strftime("%Y-%m-%d-%H")  
  174.             return start_str + ":" + end_str  
  175.         else:  
  176.             return '-'  

有了每个小时的类之后,那就可以设置开始收集的时间了。

[python]  view plain  copy
  1. def main():  
  2.     logger = logging.getLogger('main')  
  3.     logFile = './collect.log'  
  4.     logger.setLevel(logging.DEBUG)  
  5.     filehandler = logging.FileHandler(logFile)  
  6.     formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s')  
  7.     filehandler.setFormatter(formatter)  
  8.     logger.addHandler(filehandler)  
  9.   
  10.     while True:  
  11.         ## 接受键盘输入  
  12.         keyword = raw_input('Enter the keyword(type \'quit\' to exit ):')  
  13.         if keyword == 'quit':  
  14.             sys.exit()  
  15.         startTime = raw_input('Enter the start time(Format:YYYY-mm-dd-HH):')  
  16.         region = raw_input('Enter the region([BJ]11:1000,[SH]31:1000,[GZ]44:1,[CD]51:1):')  
  17.         savedir = raw_input('Enter the save directory(Like C://data//):')  
  18.         interval = raw_input('Enter the time interval( >30 and deafult:50):')  
  19.   
  20.         ##实例化收集类,收集指定关键字和起始时间的微博  
  21.         cd = CollectData(keyword, startTime, region, savedir, interval)  
  22.         while cd.flag:  
  23.             print cd.timescope  
  24.             logger.info(cd.timescope)  
  25.             url = cd.getURL()  
  26.             cd.download(url)  
  27.             cd.timescope = cd.getTimescope(cd.timescope,1)  #改变搜索的时间,到下一个小时  
  28.         else:  
  29.             cd = None  
  30.             print '-----------------------------------------------------'  
  31.             print '-----------------------------------------------------'  
  32.     else:  
  33.         logger.removeHandler(filehandler)  
  34.         logger = None  
万事俱备,跑起来吧!

[python]  view plain  copy
  1. if __name__ == '__main__':  
  2.     main()  

就这样了……

如果想编译成windows窗口文件或者想改造成自己小爬虫,欢迎去 github pull一下!!

猜你喜欢

转载自blog.csdn.net/dhdhxgx/article/details/80621006