文档章节

医学教育网批量资源下载程序之——登陆

临峰不畏
 临峰不畏
发布于 2014/12/18 22:27
字数 3266
阅读 1564
收藏 34

首先申明

To www.med66.com网站设计师:我只是想批量下载已花钱购买的资源罢了,没有恶意。


12-18

今晚接到老姐的电话,说她已在“医学教育网”订购了不少视频,要我帮她将所有的视频都下载下来。
我看了一下,里面有24门科目,每门科目有40多节。要我手动一个一个下,还不如让我去死。
这种重复的事情还是让程序来做吧!这里开一篇博客直播编写的过程。

被爬网址:http://www.med66.com/

前几天我刚做完一个Qihuiwang的爬虫软件。这次我评估了一下,这次要做的视频下载爬虫程序比上次又有新的挑战:

(1)要处理登陆的过程,上一个不需要登陆就可以直接爬。这次必须要登陆才行。涉及到post数据表的过程

(2)要识别JavaScript程序。我看一下,在我获取网页的那个按钮上写的是 onclick="goDownload('700914', ' ')。这个要转换进行转换成url地址

(3)下载需要记载哪些文件已经下载了,以免每次启动程序都从头开始下载。这是不合理的。

(4)下载的文件要以课程进行目录组织。

网站路径如下:

登陆页面 -(登陆)-> 学员课程页面 -(进入课程)-> 目录页面 -(下载中心)-> 下载页面 --> 小节视频

好,明天开搞,尽情关注!

12-19

今晚加了个班,很累了,况且容忍我好好休息一晚。明日再整!

12-22

周末跟一位做数据挖掘的朋友聊起这事儿。我那朋友说,验证码这事真不好越过。每个网站的验证码都千奇百怪的,现在没有一个统一的识别程序。不过有另一个方法,反正我有帐号密码,那么人工来识别。登陆成功之后,就以登陆后的cookie与网站进行交互。

怎么实现模拟登陆?我查了一下,网上有很多范例:

http://blog.csdn.net/lmh12506/article/details/7818306

http://www.jbxue.com/article/python/22981.html

http://www.oschina.net/code/snippet_16840_2003

12-23

今天研究了一下 www.med66.com这个网站。我用的是firefox浏览器,安装了firebug插件的。

打开该网页,找到登陆模块所对应的网页源码:

发现 src="/global/login.html?t=1419344700706",除了这个就没有别的了。这说明登陆那边的代码应该是在 /global/login.html 中描述的。

于是,我访问 http://www.med66.com/global/login.html,我们可以看到这就是登陆窗口。

想必这就是所谓的登陆页面了。详细看一下这个表单,它分三个部分:学员代码、密码、验证码。

在验证码那里,还隐藏了不少的隐性表单数据:

这些数据在提交表单的时候也必须一同提交。

登陆按钮:


12-26

昨天去了趟香港,太累了,休息一晚接着整。网络这方面,我是一个菜鸟,有大鸟漂过,不防指导指导。

上次找到了登陆界面,并得到了源码。我们需要从form中提取出表单。


表单内容:

username=用户名
passwd=密码
randcode=验证码
turl=http://member.med66.com/member/loginDispose.shtm?gotoURL=http://www.med66.com/global/login.html
furl=http://www.med66.com/global/login.html?validateLogin=n
type=JS
domain=@med66.com
randid=login
cmd=ucAuth
retfield=username

发送方式:post,发送到:http://portal.cdeledu.com/auth/index.php

登陆触发:

我查找了“登陆”按钮执行函数"loginSm()"函数,如下为该函数的主体内容:

function loginSm(){
		var flag = checkdata();
		if(flag == false){
			return;
		}
		var v_username = $.trim($("#username").val());
		var v_passwd = $.trim($("#passwd").val());
		var v_randcode = $.trim($("#randcode").val());
		$("#username").val(v_username);
		$("#passwd").val(v_passwd);
		$("#randcode").val(v_randcode);
		$.getJSON("http://"+portal+"/auth/index.php?cmd=ucAuth&type=JSON&randid=login&username="+v_username+"&passwd="+v_passwd+"&domain=@med66.com&randcode="+v_randcode+"&jsonpCallback=?",
			function(json){
				var code = json.code;
				var sid = json.sid;
				if(code == 0){
					document.getElementById("passwd_span").className="r";
					document.getElementById("username_span").className="r";
					document.getElementById("randcode_span").className="r";
					$("#submit_code").val(code);
					$("#submit_sid").val(sid);
					$("#submit_form").submit();
				}else{
					chgverify();
					document.getElementById("username_span").className="w";
					document.getElementById("passwd_span").className="w";
					document.getElementById("randcode_span").className="";
					document.getElementById("username").focus(); 
					document.getElementById("randcode").value="";
				}
		});
	
	}

我不会javascript,但看到上面的代码也能猜到点意思:
(1)从网页中获取"username", "passwd", "randcode"的值,并去除前后空格后赋值给javascript变量v_username, v_passwd, v_randcode。

var v_username = $.trim($("#username").val());
		var v_passwd = $.trim($("#passwd").val());
		var v_randcode = $.trim($("#randcode").val());
		$("#username").val(v_username);
		$("#passwd").val(v_passwd);
		$("#randcode").val(v_randcode);

(2)用.getJSON()函数访问网址,得到json数据。这个网址是由变量拼接出来的。

$.getJSON("http://"+portal+"/auth/index.php?cmd=ucAuth& ... &jsonpCallback=?", function(json){ ... } )

那么问题来了:portal的值是多少?

上面的代码,先得到流览器的类型到ua,
如果是safari浏览器,那么portal="portal.med66.com",并修改autoSubmitForm与login_form表单的action属性为"http://portal.cdeledu.com/auto/index.php"
我的浏览器不是safari,那么portal就应该是:portal.cdeledu.com

如果用户名为:cqy,密码为:123,验证码为:4996,那么要访问的网址为:

http://portal.cdeledu.com/auth/index.php?cmd=ucAuth&type=JSON&randid=login&username=cqy&passwd=123&domain=@med66.com&randcode=4996&jsonpCallback=?

我在浏览器的地址栏输入上述地址,进入。得到的返回是:

说是验证码不对。我这个验证码是从另一个页面得到了。

是不是必须得在验证码作在同一个页面提交才行?我试一下在刚刚验证码作在的页面进行访问上述网址。

果然是这样的。大家看,返回的code为0,说明成功了。而且还有sid数据。

这是为啥呢?难不成,在提交的时候,不同的页面还有一个id码?
于是我想研究一下验证码,看到网页:http://www.cnd8.com/news/news/13778.htm

说验证码都是存放在cookie里面的。于是我打开 http://www.med66.com/global/login.html 并复制一个同样的页面。这两个页网除了验证码不一样,其它都一样。然后我打开firebug,查看它们之间的cookie的差别。我反复比较,没有发现有任何的区别。得出的结论那就是验证码相关的信息并没有存放在cookie里。

那除了cookie外,那就是别的什么识别码了。

后来经反复验证,原来是之前我网址写错了或是验证码失效了。其实没有限制,只要是用firefox打开的登陆界面,但用firefox去访问那个网址,都能成功,不管是不是同个页面,或同个窗口。同一种浏览器是可以的,但不同的不行。比如说:我从firefox上得到的验证码,把网址放在konqueror中去访问,就会报验证码错误。反之在firefox上去访问就OK。konqueror得验证码,firefox访问也不行。(好像是cookie引起的问题)
既然这样,那就没什么大的问题。

(3)将json数据交给最后的回调函数进行分析处理。

var code = json.code;
				var sid = json.sid;
				if(code == 0){
					document.getElementById("passwd_span").className="r";
					document.getElementById("username_span").className="r";
					document.getElementById("randcode_span").className="r";
					$("#submit_code").val(code);
					$("#submit_sid").val(sid);
					$("#submit_form").submit();

如果成功,json.code应该为0。然后将code, sid填写到网页的submit_code, 与submit_sid元素的value中去。并触发表单的提交事件。


如上就是第二次要提交的表单,看起来非常简单。用python很好模拟的。

好家伙!原来提交过程还要与网站进行了次交互才行。

12-29

验证码是怎么得到了?

每次访问 http://portal.cdeledu.com/auth/randcode.php?id=login 都能得到一个新的码证码图片。而这个验证码可以用来获取json上面数据的(这个我验证过)。可以通过wget获取这个图片。

好,既然了解得差不多了。下面我们就来用python开始模拟这个登陆过程。

import urllib
import urllib2
import cookielib

#登陆网站,并返回登陆后的页面
def login():
    username = 'hevake'
    password = 'abc123'

    #配置opener,要使用到cookie
    install_opener()    
    #获取验证码图片,展示并让用户输入验证码
    randcode = get_randcode()   
    if randcode != None:
        #获取json数据,提取code, sid
        json_data = get_json(username, password, randcode)
        if json_data != None:
            if json_data['code'] == '0':
                #用code, sid提交submit_form表单,返回登陆后的页面
                page = submit_form(json_data['code'], json_data['sid'])
                print('登陆成功')
                return page
            else:
                print(json_data['msg'])
        else:
            print('获取json失败')
    else:
        print('获取验证码失败')
    pass

if __name__ == '__main__':
    page = login()
    if page != None:
        f = open('login.html', 'w')
        f.write(page)
        f.close()

不多解释,上面只是粗略的登陆步骤,每步还需要实现。

第一步:install_opener()

由于登陆需要用到cookie,所以要使用带cookie的opener,如下为 install_opener()函数的实现:

#配置urllib2的opener
def install_opener():
    cj = cookielib.CookieJar()
    processor = urllib2.HTTPCookieProcessor(cj)
    opener = urllib2.build_opener(processor)
    urllib2.install_opener(opener)
    pass

配置了opener之后,后面用urllib2.urlopen()就是用的新设置的opener进行操作了。

相关文章:urllib2,cookielib

第二步:get_randcode()

前面提到了,获取验证码其实就是访问网址 http://portal.cdeledu.com/auth/randcode.php?id=login,那么现在要做的是用python从这个url上获取图片,并展示出来。

相关的文章:python批量下载图片python下载网页图片

今天太晚了,明日接着搞!

2015 1-3

获取验证码流程大概如下:

def get_randcode():
    #TODO
    # 获取随机验证码
    # http://portal.cdeledu.com/auth/randcode.php?id=login
    # 将验证码展现出来,让用户输入
    randcode = raw_input('请输入验证码:')
    return randcode
    pass

本人现在还不知道怎么让计算机自动识别验证码,那就人工输入吧!反正又不是抢火车票。

用一个专门的函数专门用于下载验证码图片:

def get_randcode_jpeg():
    randcode_url = 'http://portal.cdeledu.com/auth/randcode.php?id=login'
    respond = urllib2.urlopen(randcode)
    return respond.read()
    pass

这个函数的功能就是访问验证码的url,获取图片。

然后,该怎么显示出来呢?
(1)保存到本地图片文件,然后调用系统应用打开。
(2)用什么别的图形组件打开。

python的GUI界面,我知道的有PyQt4, Tkinter。PyQt4功能是强大,但是安装很糟心!Tkinter相对比较简单。那就选用Tkinter吧。

验证码的文件是jpeg,而Tkinter显不出来,而网上相关的资源不多。那还是用别的看看,wxPython。

算了,蛮拆腾的,就为了显示个图片还得去学wxPython,Tkinter。还是采用方案(2)吧!查了一下,centos的图片浏览器命令是 eog,那就用它算了。

def show_randcode_jpeg():
    jpeg_context = get_randcode_jpeg()
    jpeg_file = 'randcode.jpeg'
    f = open(jpeg_file, 'w')
    f.write(jpeg_context)
    f.close()
    os.system('eog "%s"' % jpeg_file)

上面这个函数能用图片浏览器达到显示验证码的效果。就这样吧!以后有时间再研究wxPython。

那么对于的 get_randcode() 函数要写成这样:

def get_randcode():
    #TODO
    # 获取随机验证码
    # http://portal.cdeledu.com/auth/randcode.php?id=login
    # 将验证码展现出来,让用户输入
    show_randcode_jpeg()
    randcode = raw_input('请输入验证码:')
    return randcode
    pass

好了!现在验证码有了。下一步就是:get_json()

第三步:get_json()

上面,我们研究过,get_json()这个过程其实是合成一个url,并从这个url获取json数据。

#合成网址,获取json数据并提取sid码
def get_json(username, password, randcode):
    url_template = 'http://portal.cdeledu.com/auth/index.php?cmd=ucAuth&type=JSON&randid=login&username=%s&passwd=%s&domain=@med66.com&randcode=%s&jsonpCallback=?'
    #合成完整的url
    url_full = url_template % (username, password, randcode)
    respond = urllib2.urlopen(url_full)
    context = respond.read()
    print(context[2:-1])
    json_data = json.loads(context)
    code = json_data.code
    sid = json_data.sid
    return code, sid

上面的代码能够成功获取json数据,并能成功获得sid码。但是在用json提取数据的时候出错。

查得原因是 python json 库不支持json中的key没有双引号括起的情况。如:{aa:123},必须得是:{"aa":123}。还有,python json不能处理单引号。

解决办法之一就是用正则式re中的sub进行替换。

>>> print ss
{code:'0',sid:'vfuuf6emf80gpoet52k23pigm6'}
>>> sd = regex.sub(r'(\w+):', r'"\1":', ss)
>>> print sd
{"code":'0',"sid":'vfuuf6emf80gpoet52k23pigm6'}

那再修改一下 get_json() 函数:

def parser_json(json_context):
    #给json的key加双引号,不然python json解释会报错
    json_context = re.sub(r'(\w+):', r'"\1":', json_context)
    #将单引号转换成双引号
    json_context = re.sub(r"\'", r'"', json_context)

    json_data = json.loads(json_context)
    return json_data

#合成网址,获取json数据并提取sid码
def get_json(username, password, randcode):
    url_template = 'http://portal.cdeledu.com/auth/index.php?cmd=ucAuth&type=JSON&randid=login&username=%s&passwd=%s&domain=@med66.com&randcode=%s&jsonpCallback=?'
    #合成完整的url
    url_full = url_template % (username, password, randcode)
    respond = urllib2.urlopen(url_full)
    context = respond.read()[2:-1]  #去除开头的?(与后面的)

    json_data = parser_json(context)
    return json_data

好了,通过多次调试,通过了登陆这个过程。

第四步:提交 submit_form表单

前面研究了,submit_form表单的提交就是:

将 code, sid 数据 POST 到 http://member.med66.com/member/loginDispose.shtm

关于表单的提交网上有太多博文了,如: http://developer.51cto.com/art/201003/186364.htm

OK,let us go !

#提交submit_form表单到 http://member.med66.com/member/loginDispose.shtm
def submit_form(code, sid):
    url = 'http://member.med66.com/member/loginDispose.shtm'
    url_data = urllib.urlencode({'code':code, 'sid':sid})
    print(url_data)
    request = urllib2.Request(url, data=url_data)
    respond = urllib2.urlopen(request)
    page = respond.read()
    return page

第五步:测试

上面,我们将各个步骤都一一实现了。现在我们来验证一下是否成功登陆。测试结果是将最后提交 submit_form 得到的页面保存在 login.html 文件中。

我们用firefox打开这个文件:

这说明,登陆已经成功了!

完整的代码已提交到 osc 代码库了:http://git.oschina.net/hevake_lcj/Med66VideoDownloader

© 著作权归作者所有

临峰不畏
粉丝 221
博文 187
码字总数 98583
作品 0
深圳
架构师
私信 提问
加载中

评论(19)

临峰不畏
临峰不畏 博主

引用来自“达文西主任”的评论

很不错,验证码可以找网上的一些接口。不过别指望验证码可以百分百识别。
博主不懂js都可以 折腾出 效果出来,确实很牛。
博主这文章绝对可以上首页,给其他新手作为一个 解题思路。

谢谢21!js的问题,我还是打电话请教懂js的同学指点的。
首席安全砖家
首席安全砖家
点个赞
Nori
Nori
点赞,爬虫
金拱门
金拱门
很不错,验证码可以找网上的一些接口。不过别指望验证码可以百分百识别。
博主不懂js都可以 折腾出 效果出来,确实很牛。
博主这文章绝对可以上首页,给其他新手作为一个 解题思路。
垫底小王子
垫底小王子
点赞
zerdon
zerdon
挺不错的,加油
Codemanman
Codemanman
新人刚学python求楼主大大多多指教
请叫我赵小宝
请叫我赵小宝
只能给楼主加油了,不懂。
临峰不畏
临峰不畏 博主

引用来自“lazyphp”的评论

思路不错,博主加油。

谢谢!这方面我很菜,望多指点才是。0
lazyphp
lazyphp
思路不错,博主加油。
医学教育网批量资源下载程序之——完成

大家好! 经过好长时间的拆腾,终于实现了 “医学教育网批量资源下程序” 。 相关的博文: 医学教育网批量资源下载程序之——登陆 医学教育网批量资源下载程序之——探路 医学教育网批量资源...

临峰不畏
2015/01/18
0
1
医学教育网批量资源下载程序之——获取下载列表

在上一篇博文里,博主登陆分析了“医学教育网”里面的网页,知道了怎么获取下载的资源。 博文:http://my.oschina.net/hevakelcj/blog/366198 今天,博主在上一次分析的基础上开始写程序。 ...

临峰不畏
2015/01/11
0
0
医学教育网批量资源下载程序之——探路

在上一个博文里,博主已实现用python登陆了 www.med66.com 网站。 博文:http://my.oschina.net/hevakelcj/admin/edit-blog?blog=357852 To www.med66.com网站设计师:我只是想批量下载已花钱...

临峰不畏
2015/01/11
0
2
医学教育网批量资源下载程序之——ftp下载失败问题

这里,我遇到了一个困难。请求大神支个招。 我通过遍历网站,获得了我所要下载的资源链接。每个资源文件看起来都是从ftp文件服务器上去抓取的。 上面的资源链接为:ftp://med:w7o0nse@60.2....

临峰不畏
2015/01/16
0
2
极客学院年VIP卡原价260的F码,200出售

F码是中国最大的IT职业在线教育平台——极客学院推出的VIP时间兑换码,凭此可在极客学院官网兑换年VIP,畅享平台上所有IT技术课程。 购买请点击 http://www.bejson.com/other/jikefma/ 这个会...

废柴大叔
2015/09/22
65
0

没有更多内容

加载失败,请刷新页面

加载更多

OSChina 周日乱弹 —— 请务必让我分担他们的痛苦!

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @clouddyy :分享石元丈晴的单曲《Why》: 《Why》- 石元丈晴 手机党少年们想听歌,请使劲儿戳(这里) @一代码哥 :当他妈狗屁的程序员,天天...

小小编辑
17分钟前
44
2
php 遇到 No input file specified的解决方法

(一)IIS Noinput file specified 方法一:改PHP.ini中的doc_root行,打开ini文件注释掉此行,然后重启IIS 方法二: 请修改php.ini 找到 ; cgi.force_redirect = 1 去掉前面分号,把后面的1...

chenhongjiang
今天
7
0
MySQL 基础

一、常用命令 在命令行中,配置好环境变量后,通过cmd可以直接进入mysql命令行模式,同时列举几种常用命令 # 进入mysql数据库,密码可以先不写,打完-p后再输入,防止被别人看到mysql -u账...

华山猛男
今天
6
0
简单的博客系统(四)Django请求HTML页面视图信息--基于函数的视图

1. 编写用于查询数据的功能函数 应用目录 下的 views.py 文件通常用于保存响应各种请求的函数或类 from django.shortcuts import renderfrom .models import BlogArticles# Create your ...

ZeroBit
今天
5
0
用脚本将本地照片库批量导入到Day One中

因为目前iCloud 空间已经不足,其中95%都是照片,之前入手了DayOne,且空间没有限制,订阅费一年也不少,再加上DayOne作为一款日记App 也比较有名,功能方面最大的就是地理视图与照片视图,尤...

在山的那边
昨天
23
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部