新浪微博爬虫(模拟登录+数据解析)

来源:互联网 发布:哈工大 知乎 编辑:程序博客网 时间:2024/04/29 08:47

郑重提醒:本博客不允许转载

我将首先分章节介绍一下新浪微博爬虫设计(包括模拟登录+数据解析)的原理,如果不想看,您可以移步最下面的代码部分。

基本步骤为:新浪微博的模拟登录、爬取指定用户页面的网页源代码、原始页面解析和提取微博正文。其中新浪微博的模拟登录是前提,解析网页源代码提取正文是关键

1. 用户名加密

新浪微博的用户名加密目前采用Base64加密算法。 Base64是一种基于64个可打印字符来表示二进制数据的表示方法。由于2的6次方等于64,所以每6个比特为一个单元,对应某个可打印字符。三个字节有24个比特,对应于4个Base64单元,即3个字节需要用4个可打印字符来表示。在Base64中的可打印字符包括字母A-Z、a-z、数字0-9 ,这样共有62个字符,此外两个可打印符号根据系统的不同而有所不同。编码后的数据比原始数据略长,为原来的4/3。

Python的hashlib模块里面包含了多种哈希算法,包括了验证文件完整性的md5,sha家族数字签名算法,还有常用的base64加密算法包含于base64模块中。

2. 密码加密

新浪微博登录密码的加密算法使用RSA2。需要先创建一个rsa公钥,公钥的两个参数新浪微博都给了固定值,第一个参数是登录第一步中的pubkey,第二个参数是js加密文件中的‘10001’。这两个值需要先从16进制转换成10进制,把10001转成十进制为65537。随后加入servertime和nonce再次加密。

3. 请求新浪通行证登录

新浪微博请求登录的URL地址为:
http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.18)
请求该URL需要发送的报头信息为包括:
{ ‘entry’: ‘microblog’, ‘gateway’: ‘1’, ‘from’: ”, ‘savestate’: ‘7’, ‘userticket’: ‘1’, ‘ssosimplelogin’: ‘1’, ‘vsnf’: ‘1’, ‘vsnval’: ”, ‘su’: encodedUserName, ‘service’: ‘miniblog’, ‘servertime’: serverTime, ‘nonce’: nonce, ‘pwencode’: ‘rsa2’, ‘sp’: encodedPassWord, ‘encoding’: ‘UTF-8’, ‘prelt’: ‘115’, ‘rsakv’: rsakv, ‘url’:’http://microblog.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack’, ‘returntype’: ‘META’}
将参数组织好,POST请求,然后查看查看POST后得到的内容。该内容是一个URL重定向地址,可以检验登录是否成功。如果地址末尾为“retcode=101”则表示登录失败,为“retcode=0”则表示登录成功。
登录成功后,在body中的replace信息中的url就是我们下一步要使用的url。然后对上面的url使用GET方法来向服务器发请求,保存这次请求的Cookie信息,就是我们需要的登录Cookie。

4. 获取网页源代码

在获取Cookie之后就可以开始爬取网页了,本文使用python的第三方包urllib2。urllib2包是Python的一个获取URLs的组件。它以urlopen函数的形式提供了一个非常简单的接口,同样提供了一个比较复杂的接口来处理一般情况,例如:基础验证,cookies,代理和其他。它们通过handlers和openers的对象提供。
HTTP是基于请求和应答机制的,客户端提出请求,服务端提供应答。urllib2用一个Request对象来映射你提出的HTTP请求,通过调用urlopen并传入Request对象,返回response对象,然后调用read获取网页源代码存入文件中。

5. 数据解析

从微博平台将数据下载到本地后,网页源代码中是html代码、json值、或是 html和 json混合等,本文要进行抽取的是微博博主所发的中文微博。
首先用正则表达式对网页源代码进行解析。具体步骤为:去除Javascrip控制语句,去除CSS样式表,将
标签转为换行符,将连续的换行转化为一个,取出HTML注释,去除连续的空格,去除多余的标签,取出@后的字符。进行这些初步的处理之后按行写入文件。
第二步抽取微博信息。通过观察可以发现博主所发的微博正文都在< script>FM.view({“ns”:”pl.content.homeFeed.index”,”domid”:”Pl_Official之后,我们根据正文所在位置的class和id对微博正文进行解析。我们组合使用正则表达式和BeautifulSoup类库。BeautifulSoup库类是一个高质量HTML解析器,它将HTML文件导入后,一次性以树型结构处理完毕,然后用户可以在树型结构内方便的抵达任何一个标签或数据,具有精准定位和查找标签的优势。BeautifulSoup的劣势在于处理速度要比正则表达式要慢。因此,组合使用正则表达式和BeautifulSoup类库,可以满足本文网页解析的需要。
第三步进一步去除噪音。微博正文中会嵌入一些广告,如“360浏览器”,“百度音乐尊享版”,“百度视频”,“腾讯视频”,“新华网”等,如果该微博已被删除则会出现“抱歉,该条微博已经被删除”,另外还有一些网页提示信息如“正在加载中请稍后”,上述噪音都要进行剔除。

下面是代码部分,先是程序结构示意图:

这里写图片描述

1. WeiboSearch.py , WeiboLogin.py , WeiboEncode.py 三个文件用来实现模拟登录功能,最后功能使用 Login.py 进行调用

文件一:WeiboSearch.py

# -*- coding: utf-8 -*-import reimport json# 在 serverData 中查找 server time 和 nonce# 解析过程主要使用了正则表达式和JSONdef sServerData(serverData):    p = re.compile('\((.*)\)')    # 定义正则表达式    jsonData = p.search(serverData).group(1)  # 通过正则表达式查找并提取分组1    data = json.loads(jsonData)    serverTime = str(data['servertime'])   # 获取data中的相应字段,Json对象为一个字典    nonce = data['nonce']    pubkey = data['pubkey']    rsakv = data['rsakv']  # 获取字段    return serverTime, nonce, pubkey, rsakv#Login中解析重定位结果部分函数def sRedirectData(text):    p = re.compile('location\.replace\([\'"](.*?)[\'"]\)')    loginUrl = p.search(text).group(1)    print 'loginUrl:',loginUrl   # 输出信息,若返回值含有 'retcode = 0' 则表示登录成功    return loginUrl

文件二:WeiboEncode.py

# -*- coding: utf-8 -*-import urllibimport base64import rsaimport binascii#获取用户名和密码加密后的形式,封装成发送数据的数据包返回def PostEncode(userName, passWord, serverTime, nonce, pubkey, rsakv):    encodedUserName = GetUserName(userName)#用户名使用base64加密    encodedPassWord = get_pwd(passWord, serverTime, nonce, pubkey)#目前密码采用rsa加密    postPara = {            'entry': 'weibo',            'gateway': '1',            'from': '',            'savestate': '7',            'userticket': '1',            'ssosimplelogin': '1',            'vsnf': '1',            'vsnval': '',            'su': encodedUserName,            'service': 'miniblog',            'servertime': serverTime,            'nonce': nonce,            'pwencode': 'rsa2',            'sp': encodedPassWord,            'encoding': 'UTF-8',            'prelt': '115',            'rsakv': rsakv,            'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',            'returntype': 'META'            }    postData = urllib.urlencode(postPara)  #封装postData信息并返回    return postData#根据明文的用户名信息获取加密后的用户名def GetUserName(userName):    userNameTemp = urllib.quote(userName)    userNameEncoded = base64.encodestring(userNameTemp)[:-1]    return userNameEncoded#根据明文的密码信息加入nonce和pubkey后根据rsa加密算法的规则生成密码的密文def get_pwd(password, servertime, nonce, pubkey):    rsaPublickey = int(pubkey, 16)    key = rsa.PublicKey(rsaPublickey, 65537) #创建公钥    message = str(servertime) + '\t' + str(nonce) + '\n' + str(password) #拼接明文加密文件中得到    passwd = rsa.encrypt(message, key)  #加密    passwd = binascii.b2a_hex(passwd)  #将加密信息转换为16进制。    return passwd

文件三:weiboLogin.py

# -*- coding: utf-8 -*-# 实现新浪微博的模拟登录import urllib2import cookielib   # 加载网络编程的重要模块import WeiboEncodeimport WeiboSearchclass WeiboLogin:    #python魔法方法,当初始化该类的对象时会调用此函数    def __init__(self, user, pwd, enableProxy = False):    # enableProxy表示是否使用代理服务器,默认关闭        print "初始化新浪微博登录..."        self.userName = user        self.passWord = pwd        self.enableProxy = enableProxy  # 初始化类成员        # 在提交POST请求之前需要GET获取两个参数,得到的数据中有 "servertime" 和 "nonce" 的值,是随机的        self.serverUrl = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack&su=&rsakt=mod&client=ssologin.js(v1.4.18)&_=1407721000736"        #loginUrl用于第二步,加密后的用户名和密码POST给这个URL        self.loginUrl = "http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.18)"        #self.postHeader = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; rv:24.0) Gecko/20100101 Firefox/24.0'}        self.postHeader = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36'}        #self.postHeader = {'User-Agent': 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)'}    # 生成Cookie接下来的所有get和post请求都带上已经获取的cookie,因为稍大些的网站的登陆验证全靠cookie    def EnableCookie(self, enableProxy):        cookiejar = cookielib.LWPCookieJar()  # 建立COOKIE        cookie_support = urllib2.HTTPCookieProcessor(cookiejar)        if enableProxy:            proxy_support = urllib2.ProxyHandler({'http': 'http://122.96.59.107:843'}) # 使用代理            opener = urllib2.build_opener(proxy_support, cookie_support, urllib2.HTTPHandler)            print "Proxy enable"        else:            opener = urllib2.build_opener(cookie_support, urllib2.HTTPHandler)        urllib2.install_opener(opener)    #获取 server time 和 nonce 参数,用于编码密码    def GetServerTime(self):        print "getting server time and nonce..."        serverData = urllib2.urlopen(self.serverUrl).read()   # 获取网页内容        print 'serverData', serverData        try:            # 在JSON中提取serverTime, nonce, pubkey, rsakv字段            serverTime, nonce, pubkey, rsakv = WeiboSearch.sServerData(serverData)            print "GetServerTime success"            return serverTime, nonce, pubkey, rsakv        except:            print "解析serverData出错!"            return None    def getData(url):        request = urllib2.Request(url)        response = urllib2.urlopen(request)        content = response.read()        return content    def Login(self):  # 登录程序        self.EnableCookie(self.enableProxy)      # Cookie或代理服务器配置,调用自定义函数实现        serverTime, nonce, pubkey, rsakv = self.GetServerTime()      # 登录第一步,调用函数获取上述信息        # 准备好所有的POST参数返回postData        postData = WeiboEncode.PostEncode(self.userName, self.passWord, serverTime, nonce, pubkey, rsakv)        print "Getting postData success"        #封装request请求,获得指定URL的文本        req = urllib2.Request(self.loginUrl, postData, self.postHeader)   # 封装请求信息        result = urllib2.urlopen(req)  # 登录第二步向self.loginUrl发送用户和密码        text = result.read()    # 读取内容        #print text        """        登陆之后新浪返回的一段脚本中定义的一个进一步登陆的url        之前还都是获取参数和验证之类的,这一步才是真正的登陆        所以你还需要再一次把这个url获取到并用get登陆即可        """        try:            loginUrl = WeiboSearch.sRedirectData(text)  # 得到重定位信息后,解析得到最终跳转到的URL            urllib2.urlopen(loginUrl)  # 打开该URL后,服务器自动将用户登陆信息写入cookie,登陆成功            print loginUrl        except:            print "login failed..."            return False        print "login success"        return True

文件四:Login.py(此处注意,需要使用自己注册的新浪微博的用户名和密码替换程序中的username和pwd)

# -*- coding: utf-8 -*-import WeiboLoginimport urllib2import reimport sysreload(sys)sys.setdefaultencoding("utf-8")def login():    username = 'username'    pwd = 'pwd'      #我的新浪微博的用户名和密码    weibologin = WeiboLogin.WeiboLogin(username, pwd)   #调用模拟登录程序    if weibologin.Login():        print "登陆成功..!"  #此处没有出错则表示登录成功

然后,自己写一个测试,运行文件四的函数Login(),程序会打印一个新浪服务器返回的地址码,如果地址码最后的retcode=0,则表示登录成功,否则登录失败。

2. Getraw_HTML.py 下载指定页面的网页源代码(此处注意,含有绝对路径,如果要使用需要改成自己的路径)

# -*- coding: utf-8 -*-import WeiboLoginimport Loginimport urllib2import reimport sysreload(sys)sys.setdefaultencoding("utf-8")#调用模拟登录的程序,从网页中抓取指定URL的数据,获取原始的HTML信息存入raw_html.txt中def get_rawHTML(url):    #Login.login()    content = urllib2.urlopen(url).read()    fp_raw = open("f://emotion/mysite/weibo_crawler/raw_html.txt","w+")    fp_raw.write(content)    fp_raw.close()                #获取原始的HTML写入文件    #print "成功爬取指定网页源文件并且存入raw_html.txt"    return content   #返回值为原始的HTML文件内容if __name__ == '__main__':    Login.login()   #先调用登录函数    #url = 'http://weibo.com/yaochen?is_search=0&visible=0&is_tag=0&profile_ftype=1&page=1#feedtop'   #姚晨    url = 'http://weibo.com/fbb0916?is_search=0&visible=0&is_tag=0&profile_ftype=1&page=1#feedtop'   #自行设定要抓取的网页    get_rawHTML(url)

3.Gettext_CH.py 解析上一部分爬取到的网页源代码。注意此处含有绝对路径,自己使用时需更改

# -*- coding: utf-8 -*-import refrom bs4 import BeautifulSoupfrom Getraw_HTML import get_rawHTMLimport Login#BeautifulSooup不适合解析中文文本,尝试过但是没有成功import sysreload(sys)sys.setdefaultencoding("utf-8")  #设置默认编码,防止在写入文件时出现编码问题def filter_tags(htmlstr):    #先过滤CDATA    re_cdata = re.compile('//<!\[CDATA\[[^>]*//\]\]>',re.I)      #匹配CDATA   re.I表示忽略大小写    re_script = re.compile('<\s*script[^>]*>[^<]*<\s*/\s*script\s*>',re.I)    #Script    re_style = re.compile('<\s*style[^>]*>[^<]*<\s*/\s*style\s*>',re.I)    #style    re_br = re.compile('<br\s*?/?>')    #处理换行    re_h = re.compile('</?\w+[^>]*>')     #HTML标签    re_comment = re.compile('<!--[^>]*-->')   #HTML注释    s = re_cdata.sub('',htmlstr)  #去掉CDATA    s = re_script.sub('',s)  #去掉SCRIPT    s = re_style.sub('',s)   #去掉style    s = re_br.sub('',s)    #将br转换为换行    s = re_h.sub('',s)   #去掉HTML 标签    s = re_comment.sub('',s)   #去掉HTML注释    s = re.sub(r'\\t','',s)    s = re.sub(r'\\n\\n','',s)    s = re.sub(r'\\r','',s)    s = re.sub(r'<\/?\w+[^>]*>','',s)    #s = re.sub(r'<\\\/div\>','',s)  #去除<\/div>    #s = re.sub(r'<\\\/a\>','',s)  #去除<\/a>    #s = re.sub(r'<\\\/span\>','',s)  #去除<\/span>    #s = re.sub(r'<\\\/i\>','',s)  #去除<\/i>    #s = re.sub(r'<\\\/li\>','',s)  #去除<\/li>    s = re.sub(r'<\\\/dd\>','',s)  #去除<\/dd>    s = re.sub(r'<\\\/dl\>','',s)  #去除<\/dl>    s = re.sub(r'<\\\/dt\>','',s)  #去除<\/dt>    #s = re.sub(r'<\\\/ul\>','',s)  #去除<\/ul>    #s = re.sub(r'<\\\/em\>','',s)  #去除<\/em>    #s = re.sub(r'<\\\/p\>','',s)  #去除<\/p>    s = re.sub(r'<\\\/label\>','',s)  #去除<\/label>    s = re.sub(r'<\\\/select\>','',s)  #去除<\/select>    s = re.sub(r'<\\\/option\>','',s)  #去除<\/option>    s = re.sub(r'<\\\/tr\>','',s)  #去除<\/tr>    s = re.sub(r'<\\\/td\>','',s)  #去除<\/td>    s = re.sub(r'@[^<]*','',s)   #去掉@后字符    s = re.sub(r'<a[^>]*>[^<]*','',s)    #去掉多余的空行    blank_line = re.compile(r'(\\n)+')    s = blank_line.sub('\n',s)     #将连续换行转换成一个换行    s = s.replace('  ','')    return sdef Handel(content, fp2):    Handel_text = []    lines = content.splitlines(True)    #按行分割每行分别处理    for line in lines:        #在用正则表达式处理之前首先根据开头的内容进行匹配        if re.match(r'(\<script\>FM\.view\(\{\"ns\"\:\"pl\.content\.homeFeed\.index\"\,\"domid\"\:\"Pl_Official)(.*)', line):            #print "line",line            #调用正则表达式处理函数进行处理            temp_new = filter_tags(line)            #print "temp_new",temp_new            Handel_text.append(filter_tags(line))   #调用正则处理函数    content_chinese = ""   #初始化,最后的中文字符串    for text in Handel_text:        #print "text",text        cha_text = unicode(text,'utf-8')  #编码        #中文,空格,标点,引号,顿号,感叹号        word = re.findall(ur"[\u4e00-\u9fa5]+|,|。|:|\s|\u00A0|\u3000|'#'|\d|\u201C|\u201D|\u3001|\uFF01|\uFF1F|\u300A|\u300B|FF1B|FF08|FF09",cha_text)        if word:   #如果该句子中含有上述字符            #print "word",word            for char in word:                if char == ' ':                    content_chinese += ' '                elif char == '\s':       #三种空格的形式                    content_chinese += ' '                elif char == '\u00A0':   #中文空格                    content_chinese += ' '                elif char == '\u3000':   #英文空格                    content_chinese += ' '                elif char == '#':                    content_chinese += '\n'                else:                    content_chinese += char        content_chinese += '\n'    #循环结束,content_chinese生成    #写入文件handel.txt中    fp3 = open("f://emotion/mysite/weibo_crawler/handel.txt","w+")    fp3.write(content_chinese)    fp3.close()    #打开该文件    fp1 = open("f://emotion/mysite/weibo_crawler/handel.txt","r")    read = fp1.readlines()    pattern = re.compile(ur"[\u4e00-\u9fa5]+")  #中文文本    #fp2 = open("chinese_weibo.txt","a")  #初始化写入文件    for readline in read:        utf8_readline = unicode(readline,'utf-8')        if pattern.search(utf8_readline):   #如果在该句话中能找到中文文本则进行处理            #print readline #测试            split_readline = readline.split(' ')  #由空格对文本进行分割,split_readline是一个list            for c in split_readline:                c = re.sub(r'发布者:[.]*','',c)   #去掉“发布者”                c = re.sub(r'百度[.]*','',c)   #去掉“百度”                c = re.sub(r'正在加载中[.]*','',c)                c = re.sub(r'360安全浏览[.]*','',c)                c = re.sub(r'抱歉[.]*','',c)                c = re.sub(r'.*?高速浏览.*','',c)                #print c,len(c)                if len(c) > 16:  #提出过短的文本,utf-8编码中一个中文为3个字节长度                    fp2.write(c)                    #print "c",c    fp1.close()    #fp2.close()  #文件关闭    #print "成功解析网页提取微博并且存入chinese_weibo.txt"

4. 调用上述各部分,提供接口,方便用户使用

# -*- coding: utf-8 -*-import threadingimport Loginimport Getraw_HTMLimport Gettext_CHimport timedef Crawler(number, weibo_url):    Login.login()  #首先进行模拟登录    fp4 = open("f://emotion/mysite/weibo_crawler/chinese_weibo.txt","w+")  #初始化写入文件    for n in range(number):        n = n + 1        url = 'http://weibo.com/' + weibo_url + '?is_search=0&visible=0&is_tag=0&profile_ftype=1&page=' + str(n)        print "crawler url",url        content = Getraw_HTML.get_rawHTML(url)  # 调用获取网页源文件的函数执行        print "page %d get success and write into raw_html.txt"%n        Gettext_CH.Handel(content, fp4)         # 调用解析页面的函数        print "page %d handel success and write into chinese_weibo.txt"%n        #time.sleep(1)    fp4.close()    ########## 数据爬取完毕!    # 对爬取到的微博进行去重处理    fp = open('f://emotion/mysite/weibo_crawler/chinese_weibo.txt', 'r')    contents = []    for content in fp.readlines():        content = content.strip()        contents.append(content)    fp.close()    set_contents = list(set(contents))    set_contents.sort(key=contents.index)    fp_handel = open('f://emotion/mysite/weibo_crawler/chinese_weibo.txt', 'w+')    for content in set_contents:        fp_handel.write(content)        fp_handel.write('\n')    fp_handel.close()def main_carwler(weibo_url, page_num):    contents = {}    print "URL",weibo_url    Crawler(page_num, weibo_url)   #调用函数开始爬取    fp5 = open("f://emotion/mysite/weibo_crawler/chinese_weibo.txt", "r")    index = 1    for content in fp5.readlines():        contents[index] = content        #print "content",content        index = index + 1    new_contents = sorted(contents.items(),key=lambda e:e[0],reverse=False)   #排序    fp5.close()    return new_contentsif __name__ == '__main__':    weibo_url = "gaoxiaosong"    new_contents = main_carwler(weibo_url, 2)

整个程序完毕。如果整体运行的话,我们只需要在最后一部分的multi_crawler.py的main中的

    new_contents = main_carwler(weibo_url, 2)

部分自行设定要爬取的用户的新浪微博ID(weibo_url)和要爬取的页面数量(main_crawler的第二个参数)。即可完成爬取。

还是提醒一点,整个程序中很多地方文件读写使用了绝对路径,要使用的话请自行更改。

参考文献:
1.新浪微博模拟登录
2.新浪微博模拟登录2
3.新浪微博爬虫
4.编码问题

2 0
原创粉丝点击