python爬虫之urllib2登录并抓取HTML页面

1.前言

  • 事务管理类app的项目,需要后台导入某工学生的个人课表,选择用python登录抓取并解析HTML的方式
  • 本文记录 登录教务管理系统抓取HTML 的部分,知识点包括:
    • Http协议相关知识(熟悉可只看首部字段)
    • 验证码实现机制与session的原理
    • urllib2库的使用
  • 解析HTML部分的参见Python爬虫之正则 & BeautifulSoup4解析HTML
  • GitHub下载地址:Python源码

2.Http协议相关知识

爬虫就是根据URL获取网页的信息,而大部分我们想爬的网页都采用HTTP协议,所以一些相关的知识有必要了解

2.1 基本概念

  • 定义: HTTP协议定义了浏览器(客户进程)怎样向WWW服务器请求文档,以及服务器怎样把文档传回来,
  • 层次: 面向事物的应用层协议
  • 特点: HTTP本身是无连接(交换HTTP报文前不用建立连接)、无状态(不区分是哪个用户提交的请求)

2.2 代理服务器proxy server

  • 定义:网络实体,又称为Web cache 万维网高速缓存 ;用于把最近的请求和响应暂存在本地磁盘
  • 目的:减小链路的通信量,减小访问因特网的时延

2.3 报文结构

2.3.1 两种三部分

  • 种类:请求报文(左)和响应报文(右),如下图1
  • 报文结构:3个部分
    • 开始行:分为请求行(请求报文)和状态行(响应报文)
    • 首部行(请求头):可以有多行,用于说明浏览器、服务器或报文主体信息,最后有一空行
    • 实体主体(消息正文)

图1 :请求报文结构(左)和响应报文结构(右)
python爬虫之urllib2登录并抓取HTML页面_第1张图片

2.3.2 请求报文

  • 版本
    • HTTP1.0:每次请求都有2倍RTT的开销(建立TCP连接+发送请求报文)
    • HTTP1.1:增加持久连接功能,当页面包含多个元素时,显著减少下载时间
  • 请求方法,如下图2,常用的是POST和GET方法,主要区别:
    • POST:url链接中不显示参数
    • GET: 直接以url链接形式访问,链接中包含了所有参数

图2:请求方法
python爬虫之urllib2登录并抓取HTML页面_第2张图片

2.3.3 响应报文

状态码概览如下表1,详细参见HTTP状态码对照表
表1:响应报文的状态码

状态码 意义
1xx 表示通知消息的,如请求收到了或者正在进行处理
2xx 表示成功,如接受或知道了
3xx 表示重定向,如要完成请求还必须采取进一步的行动
4xx 表示客户的差错,如请求中有错误的语言或不能完成
5xx 表示服务器的差错,如服务器无法完成请

2.3.4 首部字段

只列出几个重要的头字段,详细参见HTTP头字段类型和HTTP头子段意义
- Host:初始URL中的主机和端口,指出请求的目的地
- User-Agent:包含客户端的浏览器与系统类型,该信息由使用的浏览器来定义,可自定义
- Connection:表示是否需要持久连接。该值为“Keep-Alive”,或者看到请求使用的是HTTP 1.1(默认进行持久连接)
- Referer:包含一个URL,用户从该URL代表的页面出发访问当前请求的页面
- Content-Length:表示请求消息正文(实体主体)的长度
- Content-Type:说明实体的文档属于什么MIME类型,或实体的内容以哪种编码格式解析,如application/xml(xml),application/json(json),application/x-www-form-urlencoded(web表单使用)
- Cookie:在HTTP服务器和用户之间传递状态信息,使网站能跟踪用户,详情往下看session的工作原理!

2.4 报文举例

  • GET报文举例:向url:http://www.baidu.com/books发出GET请求,以“key-value”形式携带参数name和num
GET /books?name=MySQL&num=8 HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) Gecko/20050225 Firefox/1.0.1
Connection: Keep-Alive
  • POST报文举例:向url:http://www.baidu.com/student发出POST请求,以“key-value”形式携带参数name和num,正文(实体主体)长度为16
POST /student HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6) Gecko/20050225 Firefox/1.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 16
Connection: Keep-Alive

name=MySQL&num=8

3.验证码实现机制与session的工作原理

对登录时有验证码的系统做爬虫,cookie必须带上,虽然urllib2有自动处理cookie的办法,但是工作过程得懂

3.1 验证码的实现机制

这里说的是数字、字母或数学算式验证码
1. 后台生成一些随机数字和字母后,将验证码正确答案放入session中
2. 创建图片,将随机生成的数字和字母嵌入图片;向图片加入一些干扰后,发给浏览器
3. 用户在浏览器中填写验证码后提交请求,后台将用户所填验证码与对应session中的验证码答案比对

3.2 session的工作原理

那么问题来了,HTTP是无状态的,上述过程中,后台怎么知道对应的是哪个session,或者说是哪个用户。看一遍工作原理就懂了
1. 当客户端访问服务器时,服务器根据需求设置session,此时会话信息(如验证码的正确答案)保存在服务器上,同时将标记session的session_id传递给客户端浏览器
2. session_id会以cookie的方式保存到浏览器的缓存中,当浏览器请求的url与该cookie的domain和path匹配,就会携带该cookie,服务器根据cookie中的session_id,就能取得客户端的数据状态(如之前存入的验证码)
3. 当浏览器关闭(或清理缓存),该cookie就会清掉。但服务器保存的session则在达到过期时间时,才会释放

4.urllib2库的使用

  • python-urllib2官网文档可以查库中所有函数的使用方法,没找到中文的,就看原汁原味的吧
  • 下面按本项目crawlCourse.py中,对urllib2语句的使用顺序做介绍,结合上面的相关知识,你会知道怎么做以及为什么这样做
  • 建议在 前言 的GitHub源码链接中下载crawlCourse.py,如果对你有帮助,就请给个star吧
  • 浏览器按F12查看请求的Headers,能够帮助你确定应该设置哪些头字段,如下图3

图3
python爬虫之urllib2登录并抓取HTML页面_第3张图片

4.1 初始化浏览器

  • 留意cookie和要每次请求都要添加的首部字段
import urllib2
import cookielib
base_url="http://xxx"
login_url="http://xxx"
captcha_url="http://xxx"
函数:__init__()
# 创建一个OpenerDirector的实例,传入一个CookieJar的Handler,用于自动处理cookie
cookie = cookielib.CookieJar()
opener = urllib2.build_opener(
    urllib2.HTTPCookieProcessor(cookie))
# 向opener中addheaders,之后用这个opener提交的request都会自动加入这些首部字段
opener.addheaders = [
            ('User-Agent', 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.100 Safari/537.36'),
            ('Connection', 'keep-alive')]
# 将上面的opener安装为全局默认浏览器
urllib2.install_opener(opener)

4.2 抓取验证码

函数:get_captcha()
urllib2.Request(captcha_url) # 构造访问captcha_url的request
# 提交该请求,获取验证码
response = opener.open(urllib2.Request(captcha_url))
for item in cookie: # 可以查看访问后的cookie信息
    print item.name, item.value
# 读取响应正文的出二进制流数据:验证码图片
data_binary_flow = response.read()
# 保存验证码图片
# response的Content-Type:image/Gif,所以验证码图片保存为.gif格式
print response.info() # 可以读取响应的信息,如headers
captcha_image = open('captcha.gif', 'wb')
captcha_image.write(data_binary_flow)
# 刷新缓冲区,把数据写到磁盘上,确保程序运行到此处图片已经更新
captcha_image.flush()
captcha_image.close()

4.3 登录系统

  • 在浏览器调试窗口Netword->Headers->Form Data能够看到登录时post的参数如下图4
  • 其中__VIEWSTATE可以在GET登录页面后正则匹配出来,当然也可以手动复制下来,如下图5
  • RadioButtonList1显示的url编码是%D1%A7%C9%FA,解码后字符是ѧ(很奇怪),而html页面里这个标签的value是“学生“,本来填了”学生“,后来发现空着也能登录成功,就空着吧
    图4 Form_data
    python爬虫之urllib2登录并抓取HTML页面_第4张图片
    图5 VIEWSTATE
    __VIEWSTATE
函数:login()
# 1.获取__VIEWSTATE的值
# GET拉取登录页面,post登录时有一个字段需要在这个页面获取
response = opener.open(login_url)
# 在HTML页面中正则匹配出__VIEWSTATE参数的值
login_page = response.read()
# (.*?) 非贪婪匹配除"\n"外的多个字符,()为分组,只获取分组内的部分
pattern_viewstate = re.compile(r'"__VIEWSTATE" value="(.*?)"')
param_viewstate = re.findall(pattern_viewstate, login_page)[0]

# 2.post:登录系统,获取首页页面
post_param = {
   '__VIEWSTATE': param_viewstate,
   'txtUserName': self.stu_id,
   'TextBox2': self.password,
   'txtSecretCode': verify_code,
   'RadioButtonList1':'',
   'Button1': '',
   'lbLanguage': '',
   'hidPdrs': '',
   'hidsc': ''}
 # Request带上参数时,就会以post方式向url提交请求
request = urllib2.Request(login_url,urllib.urlencode(post_param))
# request.add_header('Referer',login_url) # 不加也可以
response = self.opener.open(request)
home_page = response.read() # 获取首页
# 为了正确的正则匹配,统一全部用unicode编码
home_page = home_page.decode('gb2312')

4.4 获取课表页面

  • 关于unicode,gb2312等编码方面的问题参见编码演变历史
  • 提交请求时,需要填写哪些头部,F12多看浏览器信息就知道了
# 正则匹配找出课表页面的path
course_url_pattern = re.compile(ur'专业推荐课表查询
  • 学生个人课表') course_url = re.findall(course_url_pattern, home_page)[0] if len(course_url) == 0: # 若登录失败,匹配无内容 return -1 course_url = base_url + u'/' + course_url # 课程表页面url # unicode有些字符encode失败,所以先转成utf-8 request = urllib2.Request(course_url.encode('utf-8')) # 必须加Referer头字段,否则无法正常访问 request.add_header('Referer',base_url+'/xs_main.aspx?xh='+self.stu_id) response = opener.open(request) # 第二个参数,表示对解码错误的处理方式,默认是'strict':raise a UnicodeError # 课程有关的字符解码不会出错,所以选择ignore忽略其他解码错误 course_page = response.read().decode('gb2312', 'ignore')
    • 到这里已经成功的登录教务管理系统并且拉取了课程表的HTML页面,后面解析出课程信息的部分参见:Python爬虫之正则 & BeautifulSoup4解析HTML

    相关阅读

    python正则表达式对照表
    Python爬虫之正则 & BeautifulSoup4解析HTML

    参考:

    HTTP头子段意义
    python-urllib2官网文档
    Python标准库urllib2的使用细节

    如果对你有所帮助,就请点个赞吧 (^-^)

    你可能感兴趣的:(Python)