美文网首页python 爬虫计算机+技术+世界python爬虫
scrapy笔记(3)-微博模拟登录及抓取微博内容

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

作者: destino74 | 来源:发表于2015-04-23 21:08 被阅读19633次

    参考阅读

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

    1. 事前准备


    2. 微博登录分析


    2.1 截包分析

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

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

    图1
    实际上截到的包更多, 但我们只需要关注图中红色标记的那几个
    我们先来看看第一个
    图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

    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

      此时要保存该请求的cookie

    图5

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

    4 抓取微博内容


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

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

    图6
    我们可以通过正则, 取出这部分内容,然后替换response的body,再用scrapy的选择器提取其中的内容和链接,具体看代码及注释吧
    # -- coding: utf-8 --
    from scrapy import Request
    from scrapy.contrib.spiders import CrawlSpider, Rule
    from scrapy.contrib.linkextractors import LinkExtractor
    from weibo_spider.items import WeiboSpiderItem
    from weibo_spider.spiders.login_api import get_login_cookie
    
    
    class 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或者选某网站写个定时增量抓取的爬虫.

    相关文章

      网友评论

      • 574362f20e62:已经在网页上登录,获取网页登录的cookies,然后在requests的请求上加上这个cookies。这个思路行不行? 我这边试了下还是提示请登录。不知道是不是cookies设置错误了。 多谢!
        574362f20e62:@574362f20e62 已经解决,是因为fiddler中找到的cookies chrome中的cookies不一样,改成chrome中的就好了
      • 风已逝哥哥:webdriver是否好点?
      • 6c0295713bbb:qq2428375119,想做一个类似的应用,希望得到你的帮助
      • c9fe42bf5af2:你好 请问如 “_parse_response”这样的类的私有方法 怎么查看它的功能以及调用在哪里调用呢? 
      • 牛仔码农:如果用手机版微博(weibo.cn)的话,可以很好的抓取微博内容
      • 30f508e1bd79:能否发下全部源码,只是想用抓下数据做 iOS
      • 121e741aaadb:你好,不知道这个url的要求是不是需要支持https,我虽然访问成功,但是并没有任何数据
      • c253ae685821:作者你好,我取得的数据是这样:
        sinaSSOController.preloginCallBack({"retcode":0,"servertime":1448887830,"pcid":"xd-16434e27a49f7cb4d01fda79c3ab43a1e056","nonce":"4MAO8N","pubkey":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDrKjhWhmGIf6GAvdtcq9XyHHv9\nWcCQyy0kWoesJTBiiCcpKT5VBjUFCOf5qju3f0MzIxSQ+RX21jxV\/i8IpJs1P0RK\n05k8rMAtt4Sru45CqbG7\/\/s4vhjXjoeg5Bubj3OpKO4MzuH2c5iEuXd+T+noihu+\nSVknrEp5mzGB1kQkQwIDAQAB\n-----END PUBLIC KEY-----","rsakv":"1330428213","is_openlock":0,"exectime":48})

        pubkey 显然不是16进制
      • 4b7b01d49a1d:你好作者,不知道关于新浪微博用ajax分1-3次加载博文的解决方案你找到了没,感觉让scrapy默认加载三次ajax的话傻傻的...有些页面只加载了2次或者1次
        c253ae685821:@大脸没牙仔仔 你好,有个问题请教一下。文中的“weibo_rsa_n”值是从哪里取得的(文中说是从prelogin中得到的),可是我不大清楚。thanks!

      本文标题:scrapy笔记(3)-微博模拟登录及抓取微博内容

      本文链接:https://www.haomeiwen.com/subject/ywnufttx.html