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

参考阅读

基于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或者选某网站写个定时增量抓取的爬虫.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,013评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,205评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,370评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,168评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,153评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,954评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,271评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,916评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,382评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,877评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,989评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,624评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,209评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,199评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,418评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,401评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,700评论 2 345

推荐阅读更多精彩内容