使用“关键字+时间段+区域”搜集新浪微博数据的方法

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

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×cope=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。首先实现一个爬取每个小时的类。

class CollectData():
    """每小时数据收集类
        利用微博高级搜索功能,按关键字搜集一定时间范围内的微博。

        大体思路:构造URL,爬取网页,然后解析网页中的微博ID。后续利用微博API进行数据入库。本程序只负责收集微博的ID。

        登陆新浪微博,进入高级搜索,输入关键字”空气污染“,选择”实时“,时间为”2013-07-02-2:2013-07-09-2“,地区为”北京“,之后发送请求会发现地址栏变为如下:
        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

            固定地址部分:http://s.weibo.com/wb/
            关键字二次UTF-8编码:%25E7%25A9%25BA%25E6%25B0%2594%25E6%25B1%25A1%25E6%259F%2593
            排序为“实时”:xsort=time
            搜索地区:region=custom:11:1000
            搜索时间范围:timescope=custom:2013-07-02-2:2013-07-09-2
            可忽略项:Refer=g
            显示类似微博:nodup=1    注:这个选项可多收集微博,建议加上。默认不加此参数,省略了部分相似微博。
            某次请求的页数:page=1

        另外,高级搜索最多返回50页微博,那么时间间隔设置最小为宜。所以该类设置为搜集一定时间段内最多50页微博。
    """
    def __init__(self, keyword, startTime, region, savedir, interval='50', flag=True, begin_url_per = "http://s.weibo.com/weibo/"):
        self.begin_url_per = begin_url_per  #设置固定地址部分,默认为"http://s.weibo.com/weibo/",或者"http://s.weibo.com/wb/"
        self.setKeyword(keyword)    #设置关键字
        self.setStartTimescope(startTime)   #设置搜索的开始时间
        self.setRegion(region)  #设置搜索区域
        self.setSave_dir(savedir)   #设置结果的存储目录
        self.setInterval(interval)  #设置邻近网页请求之间的基础时间间隔(注意:过于频繁会被认为是机器人)
        self.setFlag(flag)  #设置
        self.logger = logging.getLogger('main.CollectData') #初始化日志

    ##设置关键字
    ##关键字需解码
    def setKeyword(self, keyword):
        self.keyword = keyword.decode('GBK').encode("utf-8")
        print 'twice encode:',self.getKeyWord()

    ##设置起始范围,间隔为1小时
    ##格式为:yyyy-mm-dd-HH
    def setStartTimescope(self, startTime):
        if not (startTime == '-'):
            self.timescope = startTime + ":" + startTime
        else:
            self.timescope = '-'

    ##设置搜索地区
    def setRegion(self, region):
        self.region = region

    ##设置结果的存储目录
    def setSave_dir(self, save_dir):
        self.save_dir = save_dir
        if not os.path.exists(self.save_dir):
            os.mkdir(self.save_dir)

    ##设置邻近网页请求之间的基础时间间隔
    def setInterval(self, interval):
        self.interval = int(interval)

    ##设置是否被认为机器人的标志。若为False,需要进入页面,手动输入验证码
    def setFlag(self, flag):
        self.flag = flag

    ##构建URL
    def getURL(self):
        return self.begin_url_per+self.getKeyWord()+"&region=custom:"+self.region+"&xsort=time×cope=custom:"+self.timescope+"&nodup=1&page="

    ##关键字需要进行两次urlencode
    def getKeyWord(self):
        once = urllib.urlencode({"kw":self.keyword})[3:]
        return urllib.urlencode({"kw":once})[3:]

    ##爬取一次请求中的所有网页,最多返回50页
    def download(self, url, maxTryNum=4):
        content = open(self.save_dir + os.sep + "weibo_ids.txt", "ab")  #向结果文件中写微博ID

        hasMore = True  #某次请求可能少于50页,设置标记,判断是否还有下一页
        isCaught = False    #某次请求被认为是机器人,设置标记,判断是否被抓住。抓住后,需要复制log中的文件,进入页面,输入验证码
        mid_filter = set([])    #过滤重复的微博ID

        i = 1   #记录本次请求所返回的页数
        while hasMore and i < 51 and (not isCaught):    #最多返回50页,对每页进行解析,并写入结果文件
            source_url = url + str(i)   #构建某页的URL
            data = ''   #存储该页的网页数据
            goon = True #网络中断标记

            ##网络不好的情况,试着尝试请求三次
            for tryNum in range(maxTryNum):
                try:
                    html = urllib2.urlopen(source_url, timeout=12)
                    data = html.read()
                    break
                except:
                    if tryNum < (maxTryNum-1):
                        time.sleep(10)
                    else:
                        print 'Internet Connect Error!'
                        self.logger.error('Internet Connect Error!')
                        self.logger.info('filePath: ' + savedir)
                        self.logger.info('url: ' + source_url)
                        self.logger.info('fileNum: ' + str(fileNum))
                        self.logger.info('page: ' + str(i))
                        self.flag = False
                        goon = False
                        break
            if goon:
                lines = data.splitlines()
                isCaught = True
                for line in lines:
                    ## 判断是否有微博内容,出现这一行,则说明没有被认为是机器人
                    if line.startswith('<script>STK && STK.pageletM && STK.pageletM.view({"pid":"pl_weibo_direct"'):
                        isCaught = False
                        n = line.find('html":"')
                        if n > 0:
                            j = line[n + 7: -12].encode("utf-8").decode('unicode_escape').encode("utf-8").replace("\\", "")
                            ## 没有更多结果页面
                            if (j.find('<div class="search_noresult">') > 0):
                                hasMore = False
                            ## 有结果的页面
                            else:
                                page = etree.HTML(j)
                                dls = page.xpath(u"//dl")   #使用xpath解析
                                for dl in dls:
                                    mid = str(dl.attrib.get('mid'))
                                    if(mid != 'None' and mid not in mid_filter):
                                        mid_filter.add(mid)
                                        content.write(mid)
                                        content.write('\n')
                        break
                lines = None
                ## 处理被认为是机器人的情况
                if isCaught:
                    print 'Be Caught!'
                    self.logger.error('Be Caught Error!')
                    self.logger.info('filePath: ' + savedir)
                    self.logger.info('url: ' + source_url)
                    self.logger.info('fileNum: ' + str(fileNum))
                    self.logger.info('page:' + str(i))
                    data = None
                    self.flag = False
                    break
                ## 没有更多结果,结束该次请求,跳到下一个请求
                if not hasMore:
                    print 'No More Results!'
                    if i == 1:
                        time.sleep(random.randint(55,75))
                    else:
                        time.sleep(15)
                    data = None
                    break
                i += 1
                ## 设置两个邻近URL请求之间的随机休眠时间,你懂的。目前没有模拟登陆
                sleeptime_one = random.randint(self.interval-30,self.interval-10)
                sleeptime_two = random.randint(self.interval+10,self.interval+30)
                if i%2 == 0:
                    sleeptime = sleeptime_two
                else:
                    sleeptime = sleeptime_one
                print 'sleeping ' + str(sleeptime) + ' seconds...'
                time.sleep(sleeptime)
            else:
                break
        content.close()
        content = None

    ##改变搜索的时间范围,有利于获取最多的数据
    def getTimescope(self, perTimescope, hours):
        if not (perTimescope=='-'):
            times_list = perTimescope.split(':')
            start_datetime = datetime.datetime.fromtimestamp(time.mktime(time.strptime(times_list[-1],"%Y-%m-%d-%H")))
            start_new_datetime = start_datetime + datetime.timedelta(seconds = 3600)
            end_new_datetime = start_new_datetime + datetime.timedelta(seconds = 3600*(hours-1))
            start_str = start_new_datetime.strftime("%Y-%m-%d-%H")
            end_str = end_new_datetime.strftime("%Y-%m-%d-%H")
            return start_str + ":" + end_str
        else:
            return '-'

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

def main():
    logger = logging.getLogger('main')
    logFile = './collect.log'
    logger.setLevel(logging.DEBUG)
    filehandler = logging.FileHandler(logFile)
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s: %(message)s')
    filehandler.setFormatter(formatter)
    logger.addHandler(filehandler)

    while True:
        ## 接受键盘输入
        keyword = raw_input('Enter the keyword(type \'quit\' to exit ):')
        if keyword == 'quit':
            sys.exit()
        startTime = raw_input('Enter the start time(Format:YYYY-mm-dd-HH):')
        region = raw_input('Enter the region([BJ]11:1000,[SH]31:1000,[GZ]44:1,[CD]51:1):')
        savedir = raw_input('Enter the save directory(Like C://data//):')
        interval = raw_input('Enter the time interval( >30 and deafult:50):')

        ##实例化收集类,收集指定关键字和起始时间的微博
        cd = CollectData(keyword, startTime, region, savedir, interval)
        while cd.flag:
            print cd.timescope
            logger.info(cd.timescope)
            url = cd.getURL()
            cd.download(url)
            cd.timescope = cd.getTimescope(cd.timescope,1)  #改变搜索的时间,到下一个小时
        else:
            cd = None
            print '-----------------------------------------------------'
            print '-----------------------------------------------------'
    else:
        logger.removeHandler(filehandler)
        logger = None

万事俱备,跑起来吧!

if __name__ == '__main__':
    main()

就这样了……

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

时间: 2024-10-17 12:46:20

使用“关键字+时间段+区域”搜集新浪微博数据的方法的相关文章

C# 将Access中时间段条件查询的数据添加到ListView中

C# 将Access中时间段条件查询的数据添加到ListView中 一.让ListView控件显示表头的方法 在窗体中添加ListView 空间,其属性中设置:View属性设置为:Detail,Columns集合中添加表头中的文字. 二.利用代码给ListView添加Item. 首先,ListView的Item属性包括Items和SubItems.必须先实例化一个ListIteView对象.具体如下: ListViewItem listViewItem=new ListViewItem(); l

slq获取某一时间段内的全部数据

条件:根据数据库原有某一时间段数据,获取在筛选时间段内的全部数据信息: 通过分析在这筛选时间段的数据可大体分为以下3种情况: 数据库某字段的开始时间在筛选的开始时间与结束时间范围内 数据库某字段的结束时间在筛选的开始时间与结束时间范围内 数据库某字段的开始时间小于筛选的开始时间,结束时间大于筛选的结束时间 转化为sql语句为: ((startDate between '筛选开始时间' and '筛选结束时间') or (endDate between '筛选开始时间' and '筛选结束时间')

抓取新浪微博数据存入MongoDB,避免重复插入微博数据的方法

def getMyDatalist(): #id这个key key = str(u'id').decode('utf-8') #存储旧数据的id列表 old_ids = [] #存储新微博的列表 extr_wb = [] #从MongoDB上获取的数据 old_datalist = weibodata.find() for old in old_datalist: old_ids.append(old[key]) #从微博上抓取新数据 data = client.statuses.home_ti

使用kettle实现关键字查询,更新单列数据

中间库---->机构代码表     本地数据库------>机构表 他们两张表的结构是这样的:   机构代码表  Id 机构表        Id    Flag 需求是这样的,我本地的机构表中的Id只要在中间库的机构代码表里存在,我就要把机构表里的Flag列更新为1,其实一开始想的是使用变量应该是可以的,但是后来才发现,它的变量,是进不去它的输入流的,所以你必须把它当成字段输入进去,所以这就是解决问题的想法 这样你的要更新的Flag就有更新的字段了,这个字段是你自己创建的一个虚拟列 使用ke

cacti监控工具之自定数据收集方法

目录 1.引语 2.使用自定义数据收集方法完成设备监控 2.1.创建数据收集脚本 2.2.定义Data Input Methods 2.3.定义Data Templates 2.4.定义Data Sources 2.5.定义Graph Templates 2.6.设置Graph Management 3.总结 1.引语 在上一博文中大致介绍了cacti的数据收集方法和模板的使用,还以一个例子介绍了怎样导入一个主机模板,并让此模板应用到一个指定的Devices上.此次博文在上一次博文的基础上介绍以

ios 保存本地数据的方法

1. NSString *path = [[NSBundle mainBundle] pathForResource:@"文件名" ofType:@"plist"]; // 文件数据类型是array NSArray *array=[NSArray arrayWithContentsOfFile:path]; //文件数据类型是*dictionary NSDictionary *dictionary = [NSDictionary dictionaryWithCont

cacti监控工具之数据收集方法、模板介绍及使用

目录 1.cacti数据收集方法.三种模板介绍 2.模板的使用介绍 1.cacti数据收集方法.三种模板介绍 在上一博文中我们部署好了cacti环境,并让cacti运行起来了.今天在这里介绍一下在cacti的"consolle"控制台中的"Collection Methods"."Templates"."Import/Export"三个部件,即在下图中标明的三个部件. 在"Collection Methods&quo

Oracle性能分析4:数据访问方法之全扫描

SQL语句执行时,Oracle的优化器会根据统计信息确定表的访问方式,一般来说,有两种基本的数据访问方式:1)全扫描.在全扫描(全表扫描或者快速全索引扫描)中,多个块被读入到一个IO运算中.2)索引扫描.索引扫描首先扫描索引叶子块以取得特定的行id(rowid),然后利用这些行id来访问父表取得实际的行数据,访问通过单块读取来完成.这里主要讲解全扫描方式,后面将介绍索引扫描. 使用全扫描 当对一个表进行全扫描时,会将表中所有数据块(block)取出并进行处理,筛选出符合条件的数据.注意Oracl

恢复oracle数据库误删除数据的方法汇总

学习数据库时,我们只是以学习的态度,考虑如何使用数据库命令语句,并未想过工作中,如果误操作一下,都可能导致无可挽回的损失.当我在工作中真正遇到这些问题时,我开始寻找答案.今天主要以oracle数据库为例,介绍关于表中数据删除的解决办法.(不考虑全库备份和利用归档日志) 删除表中数据有三种方法:·delete(删除一条记录)·drop或truncate删除表格中数据 1.delete误删除的解决方法    原理:利用oracle提供的闪回方法,如果在删除数据后还没做大量的操作(只要保证被删除数据的