【爬虫初探】新浪微博搜索爬虫实现

全文概述

功能:爬取新浪微博的搜索结果,支持高级搜索中对搜索时间的限定
网址http://s.weibo.com/
实现:采取selenium测试工具,模拟微博登录,结合PhantomJS/Firefox,分析DOM节点后,采用Xpath对节点信息进行获取,实现重要信息的抓取,并存储至Excel中。
获取的微博信息包括:博主昵称, 博主主页, 微博认证, 微博达人, 微博内容, 发布时间, 微博地址, 微博来源, 转发, 评论, 赞
这里写图片描述

代码请详见Github:weibo_search_spider

实现

一、微博登陆
一般的微博模拟登陆向服务器传送cookies,但是selenium可以通过模拟点击实现登陆,登陆时需要输入验证码。通过登陆新浪通行证(http://login.sina.com.cn/),便可以以登陆的身份打开微博。

driver = webdriver.Firefox()

def LoginWeibo(username, password):
    try:
        #输入用户名/密码登录
        print u'准备登陆Weibo.cn网站...'
        driver.get("http://login.sina.com.cn/")
        elem_user = driver.find_element_by_name("username")
        elem_user.send_keys(username) #用户名
        elem_pwd = driver.find_element_by_name("password")
        elem_pwd.send_keys(password)  #密码
        elem_sub = driver.find_element_by_xpath("//input[@class='smb_btn']")
        elem_sub.click()              #点击登陆 因无name属性

        try:
            #输入验证码
            time.sleep(10)
            elem_sub.click() 
        except:
            #不用输入验证码
            pass

        print 'Crawl in ', driver.current_url
        print u'输出Cookie键值对信息:'
        for cookie in driver.get_cookies(): 
            print cookie
            for key in cookie:
                print key, cookie[key] 
        print u'登陆成功...'
    except Exception,e:      
        print "Error: ",e
    finally:    
        print u'End LoginWeibo!\n'

注意:Firefox登陆时是否需要输入验证码在不同机器上表现不一,譬如在我电脑上无需输入验证码即可登录而在另一台服务器上需要输入验证码。所以如果需要输入验证码, 则只能通过Firefox实现,因为PhantomJS作为headless浏览器,无法实现输入验证码的功能。
这里写图片描述

二、搜索并处理结果
访问http://s.weibo.com/页面,输入关键词,点击搜索后,限定搜索的时间范围,处理页面的搜索结果。

总体调度
搜索的总调度程序如下:

def GetSearchContent(key):

    driver.get("http://s.weibo.com/")
    print '搜索热点主题:', key.decode('utf-8')

    #输入关键词并点击搜索
    item_inp = driver.find_element_by_xpath("//input[@class='searchInp_form']")
    item_inp.send_keys(key.decode('utf-8'))
    item_inp.send_keys(Keys.RETURN)    #采用点击回车直接搜索

    #获取搜索词的URL,用于后期按时间查询的URL拼接
    current_url = driver.current_url
    current_url = current_url.split('&')[0] #http://s.weibo.com/weibo/%25E7%258E%2589%25E6%25A0%2591%25E5%259C%25B0%25E9%259C%2587

    global start_stamp
    global page

    #需要抓取的开始和结束日期
    start_date = datetime.datetime(2010,4,13,0)
    end_date = datetime.datetime(2010,4,26,0)
    delta_date = datetime.timedelta(days=1)

    #每次抓取一天的数据
    start_stamp = start_date
    end_stamp = start_date + delta_date

    global outfile
    global sheet

    outfile = xlwt.Workbook(encoding = 'utf-8')

    while end_stamp <= end_date:

        page = 1

        #每一天使用一个sheet存储数据
        sheet = outfile.add_sheet(str(start_stamp.strftime("%Y-%m-%d-%H")))
        initXLS()

        #通过构建URL实现每一天的查询
        url = current_url + '&typeall=1&suball=1&timescope=custom:' + str(start_stamp.strftime("%Y-%m-%d-%H")) + ':' + str(end_stamp.strftime("%Y-%m-%d-%H")) + '&Refer=g'
        driver.get(url)

        handlePage()  #处理当前页面内容

        start_stamp = end_stamp
        end_stamp = end_stamp + delta_date

构造搜索时间
在搜索时间的构造方面,实际上有高级搜索-搜索时间选择按钮可以进行搜索时间的选择。但是对所有的行为进行selenium点击模拟似乎有些太过复杂,而且在实现的过程中发现由于两个日期的选择公用一个calendar,不知是何原因导致截至日期选择时会出错。
这里写图片描述

所以上面介绍了一种更加简便的方法:构造datetime对象并加入URL。
通过对限定时间的搜索URL进行分析可以看出,只需要在基本URL基础上加入时间的限定即可。
http://s.weibo.com/weibo/%25E7%258E%2589%25E6%25A0%2591%25E5%259C%25B0%25E9%259C%2587&typeall=1&suball=1&timescope=custom:2016-05-01-0:2016-05-02-0&Refer=g

单个页面数据处理
每一个页面加载完毕之后,需要经过一系列判断,最终决定是否有内容可以获取

#页面加载完成后,对页面内容进行处理
def handlePage():
    while True:
        #之前认为可能需要sleep等待页面加载,后来发现程序执行会等待页面加载完毕
        #sleep的作用是对付微博的反爬虫机制,抓取太快可能会判定为机器人,需要输入验证码
        time.sleep(2)
        #先行判定是否有内容
        if checkContent():
            print "getContent"
            getContent()
            #先行判定是否有下一页按钮
            if checkNext():
                #拿到下一页按钮
                next_page_btn = driver.find_element_by_xpath("//a[@class='page next S_txt1 S_line1']")
                next_page_btn.click()
            else:
                print "no Next"
                break
        else:
            print "no Content"
            break

页面加载结果判断
在这里,点击搜索按钮之后的搜索结果可能有以下几种情况

1、页面无搜索结果

这里写图片描述
这里写图片描述
如图中所示,如果在指定时间内,关键词无搜索结果(此时,页面中会有推荐微博,推荐微博很容易被当作是正常微博)。通过与有搜索结果的页面比较,此类页面最典型的特征是拥有"class = pl_noresult"的div,可以通过Xpath查找"//div[@class='pl_noresult']"的节点是否存在来判定有搜索结果,具体判定如下:

#判断页面加载完成后是否有内容
def checkContent():
    #有内容的前提是有“导航条”?错!只有一页内容的也没有导航条
    #但没有内容的前提是有“pl_noresult”
    try:
        driver.find_element_by_xpath("//div[@class='pl_noresult']")
        flag = False
    except:
        flag = True
    return flag

2、页面有搜索结果,且只有一页
这里写图片描述

通过仔细分析,这样的页面最典型的特征是没有导航条,即没有“下一页”按钮

3、页面有搜索结果,且有不止一页
这里写图片描述

类似这样的情况,最典型的特征是有“下一页”按钮,那么2和3判断的标准就可以是是否有“下一页”按钮。如果有,就可以点击进入下一页啦!

#判断是否有下一页按钮
def checkNext():
    try:
        driver.find_element_by_xpath("//a[@class='page next S_txt1 S_line1']")
        flag = True
    except:
        flag = False
    return flag

获取页面内容
在判定页面有内容的前提下,页面内容的获取是最重要的一部分,通过分析页面DOM节点,逐一获取需要的信息:
这里写图片描述

#在页面有内容的前提下,获取内容
def getContent():

    #寻找到每一条微博的class
    nodes = driver.find_elements_by_xpath("//div[@class='WB_cardwrap S_bg2 clearfix']")

    #在运行过程中微博数==0的情况,可能是微博反爬机制,需要输入验证码
    if len(nodes) == 0:
        raw_input("请在微博页面输入验证码!")
        url = driver.current_url
        driver.get(url)
        getContent()
        return

    dic = {}

    global page
    print str(start_stamp.strftime("%Y-%m-%d-%H"))
    print u'页数:', page
    page = page + 1
    print u'微博数量', len(nodes)

    for i in range(len(nodes)):
        dic[i] = []

        try:
            BZNC = nodes[i].find_element_by_xpath(".//div[@class='feed_content wbcon']/a[@class='W_texta W_fb']").text
        except:
            BZNC = ''
        print u'博主昵称:', BZNC
        dic[i].append(BZNC)

        try:
            BZZY = nodes[i].find_element_by_xpath(".//div[@class='feed_content wbcon']/a[@class='W_texta W_fb']").get_attribute("href")
        except:
            BZZY = ''
        print u'博主主页:', BZZY
        dic[i].append(BZZY)

        try:
            WBRZ = nodes[i].find_element_by_xpath(".//div[@class='feed_content wbcon']/a[@class='approve_co']").get_attribute('title')#若没有认证则不存在节点
        except:
            WBRZ = ''
        print '微博认证:', WBRZ
        dic[i].append(WBRZ)

        try:
            WBDR = nodes[i].find_element_by_xpath(".//div[@class='feed_content wbcon']/a[@class='ico_club']").get_attribute('title')#若非达人则不存在节点
        except:
            WBDR = ''
        print '微博达人:', WBDR
        dic[i].append(WBDR)

        try:
            WBNR = nodes[i].find_element_by_xpath(".//div[@class='feed_content wbcon']/p[@class='comment_txt']").text
        except:
            WBNR = ''
        print '微博内容:', WBNR
        dic[i].append(WBNR)

        try:
            FBSJ = nodes[i].find_element_by_xpath(".//div[@class='feed_from W_textb']/a[@class='W_textb']").text
        except:
            FBSJ = ''
        print u'发布时间:', FBSJ
        dic[i].append(FBSJ)

        try:
            WBDZ = nodes[i].find_element_by_xpath(".//div[@class='feed_from W_textb']/a[@class='W_textb']").get_attribute("href")
        except:
            WBDZ = ''
        print '微博地址:', WBDZ
        dic[i].append(WBDZ)

        try:
            WBLY = nodes[i].find_element_by_xpath(".//div[@class='feed_from W_textb']/a[@rel]").text
        except:
            WBLY = ''
        print '微博来源:', WBLY
        dic[i].append(WBLY)

        try:
            ZF_TEXT = nodes[i].find_element_by_xpath(".//a[@action-type='feed_list_forward']//em").text
            if ZF_TEXT == '':
                ZF = 0
            else:
                ZF = int(ZF_TEXT)
        except:
            ZF = 0
        print '转发:', ZF
        dic[i].append(str(ZF))

        try:
            PL_TEXT = nodes[i].find_element_by_xpath(".//a[@action-type='feed_list_comment']//em").text#可能没有em元素
            if PL_TEXT == '':
                PL = 0
            else:
                PL = int(PL_TEXT)
        except:
            PL = 0
        print '评论:', PL
        dic[i].append(str(PL))

        try:
            ZAN_TEXT = nodes[i].find_element_by_xpath(".//a[@action-type='feed_list_like']//em").text #可为空
            if ZAN_TEXT == '':
                ZAN = 0
            else:
                ZAN = int(ZAN_TEXT)
        except:
            ZAN = 0
        print '赞:', ZAN
        dic[i].append(str(ZAN))

        print '\n'

    #写入Excel
    writeXLS(dic)

写在最后

上一篇博客中我提到过:一般来说,数据抓取工作主要有两种方式:一是通过抓包工具(Fiddle)进行抓包分析,获取ajax请求的URL,通过URL抓取数据,这也是更为通用、推荐的方法;另外一种方法就是后面要使用的模拟浏览器行为的爬虫。
我深知,本文所使用的数据抓取方法是一种效率较为低下的方式,但另外一方面来看,也是入门较快的一种方式,只需要掌握selenium、xpath的基础语法,便可以快速地构建爬虫程序。接下来会更加深入地研究高效率的爬虫方法。
希望这种简单的思想和方法能够帮助到你,也欢迎多多交流多多指教。

感谢Eastmount九茶的帮助

(By MrHammer 2016-05-02 下午6点 @Bin House Rainy)

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值