scrapy笔记(3)-微博模拟登录及抓取微博内容

来源:互联网 发布:移动4g数据卡 编辑:程序博客网 时间:2024/05/17 01:03

参考阅读

基于python的新浪微博模拟登陆
Python模拟登录新浪微薄(使用RSA加密方式和Cookies文件
新浪微博登录rsa加密方法
模拟登录新浪微博(直接填入Cookie)
模拟登录新浪微博(Python)

1. 事前准备


  • 阅读上篇scrapy笔记(2)
  • 下载Fiddler并掌握其基本用法
  • 阅读urllib2文档
  • 下载本文我的源码

2. 微博登录分析


2.1 截包分析

以下的内容需要掌握Fiddler截包、改包重发等基本知识, 如果不想了解微博的模拟登录的流程及原理, 那么可以跳过这部分直接到第3步. 不过建议还是去熟悉下Fiddler这个前端调试神器, 当然,用其它截包工具代替也是可以的.比如Firefox的插件httpfox

微博的登录入口有好几个, 我们选择http://weibo.com/login.php 这个. 其实只要登录的逻辑不变, 其它的入口也是可以的.
然后, 我们让Fiddler开始截包, 并在登录页面上输入账号密码登录一次.
截到关于登录的包如下:

图1
图1

实际上截到的包更多, 但我们只需要关注图中红色标记的那几个
我们先来看看第一个

图2
图2

图中一栏的所有数据就是我们在模拟登录时需要填入的数据.
这些数据中除了su、sp、rsakv、servertime、nonce是经过js处理动态生成的,其它都是个固定值,可以在代码中写死.
怎么获得这些值呢?

http://login.sina.com.cn/sso/prelogin.phpentry=weibo&callback=sinaSSOController.preloginCallBack&su=yourusername&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.11)

注意上面url的su=yourusername部分, 这里的su是经过js处理后的用户名.
请求这个url可以得到servertime,nonce,pubkey,rsakv等等

图3
图3

2.2 查看json

我们还需要知道js是怎么处理我们填入的用户名及密码的, 即su与sp.
首先我们要在未登录状态到http://login.sina.com.cn/signup/signin.php?entry=sso 这个页面,并得到http://login.sina.com.cn/js/sso/ssologin.js 这个js文件.
查看ssologin.js的makeRequest函数, 原型如下:

 var makeRequest = function (username, password, savestate) {    var request = {        entry: me.getEntry(),        gateway: 1,        from: me.from,        savestate: savestate,        useticket: me.useTicket ? 1 : 0    };    if (me.failRedirect) {        me.loginExtraQuery.frd = 1    }    request = objMerge(request, {pagerefer: document.referrer || ""});    request = objMerge(request, me.loginExtraFlag);    request = objMerge(request, me.loginExtraQuery);    request.su = sinaSSOEncoder.base64.encode(urlencode(username));    if (me.service) {        request.service = me.service    }    if ((me.loginType & rsa) && me.servertime && sinaSSOEncoder && sinaSSOEncoder.RSAKey) {        request.servertime = me.servertime;        request.nonce = me.nonce;        request.pwencode = "rsa2";        request.rsakv = me.rsakv;        var RSAKey = new sinaSSOEncoder.RSAKey();        RSAKey.setPublic(me.rsaPubkey, "10001");        password = RSAKey.encrypt([me.servertime, me.nonce].join("\t") + "\n" + password)    } else {        if ((me.loginType & wsse) && me.servertime && sinaSSOEncoder && sinaSSOEncoder.hex_sha1) {            request.servertime = me.servertime;            request.nonce = me.nonce;            request.pwencode = "wsse";            password = sinaSSOEncoder.hex_sha1("" + sinaSSOEncoder.hex_sha1(sinaSSOEncoder.hex_sha1(password)) + me.servertime + me.nonce)        }    }    request.sp = password;    try {        request.sr = window.screen.width + "*" + window.screen.height    } catch (e) {    }    return request};

从代码中我们可以知道su就是经过html字符转义再转成base64编码
在python中我们可以这样转化:

def get_su(user_name):    username_ = urllib.quote(user_name)     # html字符转义    username = base64.encodestring(username_)[:-1]    return username

再看sp, 关于密码的这部分有点复杂, 我自己对密码学这部分并不大了解, 不过可以从js中看到, weibo登录对密码有两种加密方式:rsa2与wsse,我们从图2的pwnencode=rsa2可知, js处理走的是这一部分逻辑(至于另一部分wsse是什么时候用到, 我不清楚)

if ((me.loginType & rsa) && me.servertime && sinaSSOEncoder && sinaSSOEncoder.RSAKey) {    request.servertime = me.servertime;    request.nonce = me.nonce;    request.pwencode = "rsa2";    request.rsakv = me.rsakv;    var RSAKey = new sinaSSOEncoder.RSAKey();    RSAKey.setPublic(me.rsaPubkey, "10001");    password = RSAKey.encrypt([me.servertime, me.nonce].join("\t") + "\n" + password)}

可以看到servertime, nonce, rsakv都被用上了.我们只要把这部分js在python中转义就行了.
我也是看别人的文章才知道,0x10001要转化成10进制的65537, 还有要经过servertime + +'\t' + nonce + '\n' + passwd拼接字符串再进行Rsa加密, 最后转成16进制即得到sp. 代码如下

def get_sp_rsa(passwd, servertime, nonce):    # 这个值可以在prelogin得到,因为是固定值,所以写死在这里    weibo_rsa_n = 'EB2A38568661887FA180BDDB5CABD5F21C7BFD59C090CB2D245A87AC253062882729293E5506350508E7F9AA3BB77F4333231490F915F6D63C55FE2F08A49B353F444AD3993CACC02DB784ABBB8E42A9B1BBFFFB38BE18D78E87A0E41B9B8F73A928EE0CCEE1F6739884B9777E4FE9E88A1BBE495927AC4A799B3181D6442443'    weibo_rsa_e = 65537  # 10001对应的10进制    message = str(servertime) + '\t' + str(nonce) + '\n' + passwd    key = rsa.PublicKey(int(weibo_rsa_n, 16), weibo_rsa_e)    encropy_pwd = rsa.encrypt(message, key)    return binascii.b2a_hex(encropy_pwd)

3 模拟登录


准备工作做完了,我们要模拟正常登录那样发几个http包, 基本上是以下几个步骤

  • http://login.sina.com.cn/sso/prelogin.phpentry=weibo&callback=sinaSSOController.preloginCallBack&su=yourusername&rsakt=mod&checkpin=1&client=ssologin.js(v1.4.11)

    获取servertime,nonce,rsakv等值

  • 把这些值与其它固定值一起提交到http://login.sina.com.cn/sso/login.php?client=ssologin.js(v1.4.11), 这个地址会跳转到passport.weibo.com/wbsso/login/(省略, 见图2)/,并返回图2下划线标注的我们需要的地址.
  • 用正则表达式取出图2的地址并请求, 得到如下结果则登录成功
    图4
    图4
    此时要保存该请求的cookie
图5
图5

以后每次抓取微博时的请求带上该cookie即可.
到这里, 模拟登录就算完成了. 当然这是人工的模拟登录与结合scrapy的模拟登录还是有所不同,不过区别也不会大到哪去, 只要把保存的cookie持久化到文件, scrapy每次请求时带上这个cookie就可以了,相信这部分不会有多大难度. 如果还是有困难, 请提出来, 有时间我再补完这部分内容.

4 抓取微博内容


4.24 补充说明,本来以为微博抓取内容跟其它一样简单,结果发现微博用js渲染所有内容,scrapy抓的网页源文件一个链接都没有, 考虑用其它方法解决, 正在坑中, 搞定后再来更新文章吧.

4.26 抓取微博内容, 搞了很久, 终于算是实现了.说说我的方法吧, 根据观察,微博的内容放在页面上某个script标签内.

图6
图6

我们可以通过正则, 取出这部分内容,然后替换response的body,再用scrapy的选择器提取其中的内容和链接,具体看代码及注释吧

# -*- coding: utf-8 -*-from scrapy import Requestfrom scrapy.contrib.spiders import CrawlSpider, Rulefrom scrapy.contrib.linkextractors import LinkExtractorfrom weibo_spider.items import WeiboSpiderItemfrom weibo_spider.spiders.login_api import get_login_cookieclass WeiboSpider(CrawlSpider):    name = 'weibo'    allowed_domains = ['weibo.com']    start_urls = ['http://www.weibo.com/u/1876296184']  # 不加www,则匹配不到cookie, get_login_cookie()方法正则代完善    rules = (        Rule(LinkExtractor(allow=r'^http:\/\/(www\.)?weibo.com/[a-z]/.*'),  # 微博个人页面的规则,或/u/或/n/后面跟一串数字             process_request='process_request',             callback='parse_item', follow=True), )    cookies = None    def process_request(self, request):        request = request.replace(**{'cookies': self.cookies})        return request    def start_requests(self):        for url in self.start_urls:            if not self.cookies:                self.cookies = get_login_cookie(url)    # 得到该url下的cookie            yield Request(url, dont_filter=True, cookies=self.cookies, meta={'cookiejar': 1})  # 这里填入保存的cookies    def extract_weibo_response(self, response):     # 提取script里的weibo内容,替换response        script_set = response.xpath('//script')        script = ''        for s in script_set:            try:                s_text = s.xpath('text()').extract()[0].encode('utf8').replace(r'\"', r'"').replace(r'\/', r'/')            except:                return response            if s_text.find('WB_feed_detail') > 0:                script = s_text                break        kw = {'body': script}        response = response.replace(**kw)        return response    def _parse_response(self, response, callback, cb_kwargs, follow=True):  # 继承crawlspider这个方法,这个方法在解析页面/提取链接前调用        response = self.extract_weibo_response(response)        return super(WeiboSpider, self)._parse_response(response, callback, cb_kwargs, follow)    def parse_item(self, response):        msg_nodes = response.xpath('//*[@class="WB_feed WB_feed_profile"][2]/div')  # 提取weibo的内容div        items = []        if msg_nodes:            for msg in msg_nodes:                item = WeiboSpiderItem()                try:                    c = msg.xpath('.//div[@class="WB_detail"]/div/text()').extract()[0]   #提取每条微博的内容,不包括at人                    content = c[38:].encode('utf8')  #从38位开始, 是为了去掉\n和一大堆空格                except Exception, e:                    pass                else:                    item['content'] = content                    item['url'] = response.url                    items.append(item)        return items

继承CrawlSpider类是因为要用到它根据定制的Rule提取/跟进链接的功能, 当然你也可以选择最基础的Spider类,不过其中的parse方法就得自己写了.

不过这份代码仍然有些问题:

  1. 暂时只能提取某人weibo第1页内容
  2. weibo向下拉滚动条会新增内容,而这部分是通过ajax动态请求json实现的,暂时只能提取第1页第1段内容

解决思路是有的
这是weibo单页地址
http://www.weibo.com/u/1832810372page=1 改page即可跳页
这是weibo分段地址
http://weibo.com/p/aj/v6/mblog/mbloglist?domain=100505&page=2&pre_page=3&pagebar=1&id=1005053190764044
domain与id可以在页面上第2个script标签内找 ,替换page和pre_page即可加载不同段的json
只要在抓取某人首页时请求page=?与pre_page=1,2,3(每页最多只有3段),就可以实现加载任一页所有内容.

于是问题来了,在scrpay中哪里嵌入这些逻辑呢?我还没想好,但我觉得如果写个小爬虫的话,用urllib2/request+beatifulsoup自己写肯定比用scrpay舒爽的多.
说实话,改出上面的代码已经折腾了我将近1天多的时间,这还是跟踪源码好半天,搞清楚这些调用的来龙去脉才弄出来的,官方的文档实在不够看.
也许该去抓移动端的包来解析一下,感觉这应该比抓PC端简单很多.

总结:


本章尽在折腾,跟踪weibo各种包并解析,同时为了达到效果跟踪调试scrapy的源码,最后也只搞出个半成品,就先挖个坑在这吧.有需要的话才花时间折腾一番.

下篇内容应该是介绍一下如何跟踪调试scrapy或者选某网站写个定时增量抓取的爬虫.



文/destino74(简书作者)
原文链接:http://www.jianshu.com/p/36a39ea71bfd
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
0 0
原创粉丝点击