郑重提醒:本博客不允许转载
我将首先分章节介绍一下新浪微博爬虫设计(包括模拟登录+数据解析)的原理,如果不想看,您可以移步最下面的代码部分。
基本步骤为:新浪微博的模拟登录、爬取指定用户页面的网页源代码、原始页面解析和提取微博正文。其中新浪微博的模拟登录是前提,解析网页源代码提取正文是关键
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 re
import json
# 在 serverData 中查找 server time 和 nonce
# 解析过程主要使用了正则表达式和JSON
def 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 urllib
import base64
import rsa
import 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 urllib2
import cookielib # 加载网络编程的重要模块
import WeiboEncode
import WeiboSearch
class 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 WeiboLogin
import urllib2
import re
import sys
reload(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 WeiboLogin
import Login
import urllib2
import re
import sys
reload(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 re
from bs4 import BeautifulSoup
from Getraw_HTML import get_rawHTML
import Login
#BeautifulSooup不适合解析中文文本,尝试过但是没有成功
import sys
reload(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 s
def 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 threading
import Login
import Getraw_HTML
import Gettext_CH
import time
def 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_contents
if __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.编码问题