新浪微博爬虫(模拟登录+数据解析)
来源:互联网 发布:哈工大 知乎 编辑:程序博客网 时间: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.编码问题
- 新浪微博爬虫(模拟登录+数据解析)
- 新浪微博爬虫模拟登录(爬手机版)
- 模拟登录新浪微博
- 使用firefox分析新浪微博登录参数(供爬虫模拟登录用)
- JAVA新浪微博爬虫设计(1)登录
- JAVA新浪微博爬虫设计(2)解析页面提取数据
- [Javascript] 爬虫 模拟新浪微博登陆
- 爬取新浪微博数据+新浪微博模拟登录+mysql+python
- 全程模拟新浪微博登录(2015)
- java 模拟登录新浪微博(通过cookie)
- httpClient4模拟登录新浪微博
- 模拟登录新浪微博(Python)
- scrapy模拟登录新浪微博
- node.js 模拟登录新浪微博
- C# 新浪微博模拟登录
- 模拟新浪微博wap登录
- java实现模拟登录新浪微博
- Python模拟新浪微博登录
- eclipse调试
- 黑马程序员——Java中的异常和包
- foundation-数组的遍历
- Codeforces #310 Div 1 简要题解
- ConcurrentModificationException异常解决办法
- 新浪微博爬虫(模拟登录+数据解析)
- ODBC与JDBC比较
- [USACO Nov08]玩具toys解题报告
- 有人照顾有人不照顾
- Java向Oracle数据库插入时间
- Ubuntu的回收站
- 【java0006】多线程 - Thread VS Runnable
- IOS--OC--LessonBlock 块语法
- 黑马程序员——Java基础:GUI