为了以后的深度学习可以爬取更多更好的语料以及其他资源,先拿weibo移动端试了下手(果然一进就全是坑~)
具体scrapy可以联系我获取哦~
1、分析weibo登陆以获取cookies
1)预请求获取服务器信息
- 请求的url
pre_url = "http://login.sina.com.cn/sso/prelogin.php?entry=weibo&callback=sinaSSOController.preloginCallBack" \
"&su=" + su + "&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.19)&_=" + str(int(time.time() * 1000))
- 其中su是加密后的用户名 最后的是当前时间戳
- su的加密方式如下
username_quote = quote_plus(self.username)
username_base64 = base64.b64encode(username_quote.encode("utf-8"))
return username_base64.decode("utf-8")
-
得到服务器信息用于构建请求登陆的url
-
具体的含义就不解释啦 需要的小伙伴可自行百度~
2)构建请求登陆url
self.form_data = {
'entry': 'weibo',
'gateway': '1',
'from': '',
'savestate': '7',
'useticket': '1',
'pagerefer': "https://passport.weibo.com",
'vsnf': '1',
'su': su,
'service': 'miniblog',
'servertime': server_time,
'nonce': nonce,
'pwencode': 'rsa2',
'rsakv': rsakv,
'sp': password_secret,
'sr': '1366*768',
'encoding': 'UTF-8',
'prelt': '115',
"cdult": "38",
'url': 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
'returntype': 'TEXT'
}
- 其中密码加密
"""对密码进行 RSA 的加密"""
rsaPublickey = int(pubkey, 16)
key = rsa.PublicKey(rsaPublickey, 65537) # 创建公钥
message = str(server_time) + '\t' + str(nonce) + '\n' + str(self.password) # 拼接明文js加密文件中得到
message = message.encode("utf-8")
ps = rsa.encrypt(message, key) # 加密
ps = binascii.b2a_hex(ps) # 将加密信息转换为16进制
return ps
- 登陆url
login_url = 'https://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.19)&_'
login_url = login_url + str(time.time() * 1000)
- 一般需要输入验证码 利用服务器数据pcid 请求并获取验证码
- 请求验证码url
cha_url = "https://login.sina.com.cn/cgi/pin.php?r="
cha_url = cha_url + str(int(random.random() * 100000000)) + "&s=0&p=" + p_cid
- 保存到本地并打开输出即可
- 后期会使用神经网络进行验证码识别
3)登陆跳转
- 从登陆请求中得到的response
- 提取ticket
'ticket': 'XXX'
- 并从其中匹配出ssosavestate
save_pattern = r'==-(\d+)-'
ssosavestate = int(re.findall(save_pattern, ticket)[0]) + 3600 * 7
- 构造params v1.4.18->v1.4.19
"callback": "sinaSSOController.callbackLoginStatus",
"ticket": ticket,
"ssosavestate": str(ssosavestate),
"client": "ssologin.js(v1.4.19)",
"_": str(time.time() * 1000),
- 以及url和header
jump_url = "https://passport.weibo.com/wbsso/login"
jump_headers = {
"Host": "passport.weibo.com",
"Referer": "https://weibo.com/",
"User-Agent": headers["User-Agent"]
}
- 再从respones获取uid 构造url
web_wb_url = "http://weibo.com/%s/profile?topnav=1&wvr=6&is_all=1" % uuid_res
- 跳转m站请求
https://passport.weibo.cn/sso/crossdomain?service=sinawap&display=0&ssosavestate='ssosavestate'&url=https%3A%2F%2Fm.weibo.cn%2Fdetail%2F4513513248445166%3Fsudaref%3Dlogin.sina.com.cn&display=0&ticket='ticket'&retcode=0
- 登陆已经成功 下一步跳转m站
m_Params = {
"url": "https://m.weibo.cn/",
"_rand": _rand,
"gateway": "1",
"service": "sinawap",
"entry": "sinawap",
"useticket": "1",
"returntype": "META",
"sudaref": "",
"_client_version": "0.6.26",
}
m_url = "https://login.sina.com.cn/sso/login.php"
- 保存cookies 使用了LWPCookieJar
self.session.cookies = ck.LWPCookieJar(filename=self.cookies_path)
# 保存
self.session.cookies.save()
- 扫描cookies文件
for file in os.listdir(cookie_files):
if file.endswith('.txt'):
cookies_temp = ck.LWPCookieJar(cookie_files + "/" + file)
cookies_temp.load(ignore_discard=True, ignore_expires=True)
self.cookie_dict.append(requests.utils.dict_from_cookiejar(cookies_temp))
- 在中间件随机或者顺抽取一个即可 因为访问需要带上cookies哦~
通过此次爬虫测试 发现m站对ip地址ban得不狠 但还是不能太频繁 否则也会被“求着歇歇” 反而是对账号比较敏感 账号可以说是越多越好 三个感觉已经足够 但文明爬虫 请求的频率还是得慢点~
2、m站请求隐藏细节
1)分析请求
- ajax加载方式
- 首页评论加载的请求 是固定的 只有id和mid不同 都是博客id
https://m.weibo.cn/comments/hotflow?id=4513513248445166&mid=4513513248445166&max_id_type=0
- 下一页的请求
https://m.weibo.cn/comments/hotflow?id=4513513248445166&mid=4513513248445166&max_id=216909405545407&max_id_type=0
-
不同之处
-
1、多了max_id
-
2、max_id_type的值也不固定为0
-
那么该去哪里找max_id和max_id_type
-
再看response
-
当前页面的max_id和max_id_type可以在上一页的response中找到
-
如此一来 就可以愉快的进行爬取啦~
-
子评论
-
子评论首页请求
https://m.weibo.cn/comments/hotFlowChild?cid=4513525815570069&max_id=0&max_id_type=0
-
cid:父评论的id
-
max_id:与父评论max_id一致
-
max_id_type:与父评论max_id_type一致
-
子评论其他页请求
https://m.weibo.cn/comments/hotFlowChild?cid=4513525815570069&max_id=4513615728599918&max_id_type=0
-
与父级评论一致 只需要确保cid唯一即可
-
注意事项
-
1、首页评论请求可以无限次访问
-
2、其他页请求只能访问两次 再多就会返回{‘ok’:‘0’}
-
3、严格按照顺序访问
-
4、并且max_id是递减的 个人觉得其生成方式是按照先后顺序的 就是对于某一页评论 不是单纯的当前博客评论页 而是所有的博客的评论页生成的先后顺序
-
5、max_id_type目前值见到0/1 并且当max_id_type为0时 max_id长度比较长 当max_id_type为1时 max_id长度比较短 猜测是两个不同的生成原理
-
6、最后一页判断 max_id会再次返回来0
3、scrapy开启爬取
话不多说 上代码~
- 下载中间件
DOWNLOADER_MIDDLEWARES = {
'mWeiBo.middlewares.MweiboDownloaderMiddleware': 543,
'mWeiBo.middlewares.UserAgentDownloaderMiddleware': 300,
'mWeiBo.middlewares.CookiesDownloaderMiddleware': 200,
'mWeiBo.middlewares.IpProxyDownloaderMiddleware': 100,
}
- spider主逻辑
class MweiboSpider(scrapy.Spider):
name = 'mweibo'
allowed_domains = ['m.weibo.cn']
# 初始化
def __init__(self):
super(MweiboSpider, self).__init__()
self.count = 0
# 博客id
self.wb_id = '4513496224687345'
self.url = 'https://m.weibo.cn/comments/hotflow?id=' + self.wb_id + '&mid=' + self.wb_id + '&max_id_type=0'
# 加载cookie
cookies_path = "/Users/sugarmei/mWeiBo/mWeiBo/spiders/cookies_files/cookies2.txt"
cookies = ck.LWPCookieJar(cookies_path)
cookies.load(ignore_discard=True, ignore_expires=True)
# 将cookie转换成字典
self.cookie_dict = requests.utils.dict_from_cookiejar(cookies)
def start_requests(self):
# 开始发布请求任务
yield scrapy.Request(url=self.url, cookies=self.cookie_dict, callback=self.parse, errback=self.error)
def parse(self, response):
# 当前ip被ban 继续ip代理请求
if response.status == 403:
yield scrapy.Request(url=response.url, cookies=self.cookie_dict, callback=self.parse, dont_filter=True)
# 发生异常
data = json.loads(response.text)
if data['ok'] == 0:
time.sleep(2)
print("请求数据为空 重新发出请求")
yield scrapy.Request(url=response.url, cookies=self.cookie_dict, callback=self.parse, dont_filter=True)
else:
# 拼接url
max_id = data['data']['max_id']
max_id_type = data['data']['max_id_type']
if data['data']['max_id'] == 0:
print("此次评论爬取结束!")
return
url_max = 'https://m.weibo.cn/comments/hotflow?id=' + self.wb_id + '&mid=' + self.wb_id + '&max_id=' + str(
max_id) + '&max_id_type=' + str(
max_id_type)
# 拿到其中的text
for comment in data['data']['data']:
# 数据持久化
item = MweiboItem()
item['username'] = comment['text']
item['comment'] = comment['user']['screen_name']
self.count += 1
yield item
print("当前获取了共 " + str(self.count) + " 条评论")
yield scrapy.Request(url=url_max, cookies=self.cookie_dict, callback=self.parse, dont_filter=False)
# more_info_type为0的评论没有回复
if comment['more_info_type'] != 0:
print('该评论有人回复', "回复数量:", comment['total_number'])
# 每次的cid都不一样 但是需要获取子评论 都要从
cid = comment['rootid']
# 在循环里面发出爬取该评论回复的请求max_id=0&max_id_type=0参数开始
url_sub = "https://m.weibo.cn/comments/hotFlowChild?cid=" + cid + "&max_id=0&max_id_type=0"
yield scrapy.Request(url=url_sub, cookies=self.cookie_dict, callback=self.parse_sub,
dont_filter=False)
# 处理子评论
def parse_sub(self, response):
# 有些评论是没有回复的 需要做判断
data = json.loads(response.text)
if data['ok'] == 0:
yield scrapy.Request(url=response.url, cookies=self.cookie_dict, callback=self.parse_sub, dont_filter=True)
else:
# 与之前一致 先找到max_id和max_id_type
max_id = data['max_id']
max_id_type = data['max_id_type']
cid = data['rootComment'][0]['id']
# 结束条件
if max_id == 0:
print(str(cid) + ":子评论爬取结束!")
return
# 否则
url_max_sub = "https://m.weibo.cn/comments/hotFlowChild?cid=" + str(cid) + "&max_id=" + str(
max_id) + "&max_id_type=" + str(max_id_type)
for sub_comment in data['data']:
# 数据库持久化
# 数据持久化
item = MweiboItem()
item['rootid'] = str(cid)
item['refid'] = '0'
item['comment'] = sub_comment['text']
item['username'] = sub_comment['user']['screen_name']
self.sub_count += 1
yield item
yield scrapy.Request(url=url_max_sub, cookies=self.cookie_dict, callback=self.parse_sub, dont_filter=True)
- 有其他想法的朋友可以一起交流哦 需要完整代码的也可以联系我