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

    上学期参加了一个大数据比赛,需要抓取大量数据,于是我从新浪微博下手,本来准备使用新浪的API的,无奈新浪并没有开放关键字搜索的API,所以只能用爬虫来获取了。幸运的是,新浪提供了一个高级搜索功能,为我们爬取数据提供了一个很好的切入点。

【python网络编程】新浪爬虫:关键词搜索爬取微博数据_第1张图片

        在查阅了一些资料,参考了一些爬虫的例子后,得到大体思路:构造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(第一页可不加)

我们查看一下网页源代码看看有什么鬼:

【python网络编程】新浪爬虫:关键词搜索爬取微博数据_第2张图片


    小伙伴们第一次看到肯定大呼我的天啊,真的是看的眼花缭乱。

    别着急,让我娓娓道来。

    首先,我们定位到图示的地方,即出现字符串<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网络编程】新浪爬虫:关键词搜索爬取微博数据_第3张图片


代码:

# coding: utf-8

'''
以关键词收集新浪微博
'''
import wx
import sys
import urllib
import urllib2
import re
import json
import hashlib
import os
import time
from datetime import datetime
from datetime import timedelta
import random
from lxml import etree
import logging
import xlwt
import xlrd
from xlutils.copy import copy


class CollectData():
    """数据收集类
	   利用微博高级搜索功能,按关键字搜集一定时间范围内的微博。
    """
    def __init__(self, keyword, startTime, interval='50', flag=True, begin_url_per = "http://s.weibo.com/weibo/"):
        self.begin_url_per = begin_url_per  #设置固定地址部分
        self.setKeyword(keyword)    #设置关键字
        self.setStartTimescope(startTime)   #设置搜索的开始时间
        #self.setRegion(region)  #设置搜索区域
        self.setInterval(interval)  #设置邻近网页请求之间的基础时间间隔(注意:过于频繁会被认为是机器人)
        self.setFlag(flag)  
        self.logger = logging.getLogger('main.CollectData') #初始化日志

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

    ##关键字需要进行两次urlencode
    def getKeyWord(self):
        once = urllib.urlencode({"kw":self.keyword})[3:]
        return urllib.urlencode({"kw":once})[3:]		
		
    ##设置起始范围,间隔为1天
    ##格式为:yyyy-mm-dd
    def setStartTimescope(self, startTime):
        if not (startTime == '-'):
            self.timescope = startTime + ":" + startTime
        else:
            self.timescope = '-'

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

    ##设置邻近网页请求之间的基础时间间隔
    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()+"&typeall=1&suball=1×cope=custom:"+self.timescope+"&page="

    ##爬取一次请求中的所有网页,最多返回50页
    def download(self, url, maxTryNum=4):
        hasMore = True  #某次请求可能少于50页,设置标记,判断是否还有下一页
        isCaught = False    #某次请求被认为是机器人,设置标记,判断是否被抓住。抓住后,需要,进入页面,输入验证码
        name_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('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:
                                #此处j要decode,因为上面j被encode成utf-8了
                                page = etree.HTML(j.decode('utf-8'))
                                ps = page.xpath("//p[@node-type='feed_list_content']")   #使用xpath解析得到微博内容
                                addrs = page.xpath("//a[@class='W_texta W_fb']")   #使用xpath解析得到博主地址
                                addri = 0
                                #获取昵称和微博内容
                                for p in ps:
                                    name = p.attrib.get('nick-name')	#获取昵称
                                    txt = p.xpath('string(.)')			#获取微博内容
                                    addr = addrs[addri].attrib.get('href')  #获取微博地址
                                    addri += 1
                                    if(name != 'None' and str(txt) != 'None' and name not in name_filter):  #导出数据到excel中
                                        name_filter.add(name)
                                        oldWb = xlrd.open_workbook('weiboData.xls', formatting_info=True)
                                        oldWs = oldWb.sheet_by_index(0)
                                        rows = int(oldWs.cell(0,0).value)
                                        newWb = copy(oldWb)
                                        newWs = newWb.get_sheet(0)
                                        newWs.write(rows, 0, str(rows))
                                        newWs.write(rows, 1, name)
                                        newWs.write(rows, 2, self.timescope)
                                        newWs.write(rows, 3, addr)
                                        newWs.write(rows, 4, txt)
                                        newWs.write(0, 0, str(rows+1))
                                        newWb.save('weiboData.xls')
                                        print "save with same name ok"
                        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(3,8))
                    else:
                        time.sleep(10)
                    data = None
                    break
                i += 1
                ## 设置两个邻近URL请求之间的随机休眠时间,防止Be Caught
                sleeptime_one = random.randint(self.interval-25,self.interval-15)
                sleeptime_two = random.randint(self.interval-15,self.interval)
                if i%2 == 0:
                    sleeptime = sleeptime_two
                else:
                    sleeptime = sleeptime_one
                print 'sleeping ' + str(sleeptime) + ' seconds...'
                time.sleep(sleeptime)
            else:
                break

    ##改变搜索的时间范围,有利于获取最多的数据   
    def getTimescope(self, perTimescope):
        if not (perTimescope=='-'):
            times_list = perTimescope.split(':')
            start_date =  datetime(int(times_list[-1][0:4]),  int(times_list[-1][5:7]), int(times_list[-1][8:10]) ) 
            start_new_date = start_date + timedelta(days = 1)
            start_str = start_new_date.strftime("%Y-%m-%d")
            return start_str + ":" + start_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):')
        #region = raw_input('Enter the region([BJ]11:1000,[SH]31:1000,[GZ]44:1,[CD]51:1):')
        interval = raw_input('Enter the time interval( >30 and deafult:50):')

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

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


你可能感兴趣的:(python,爬虫,数据,微博,搜索)