全栈工程师开发手册 (作者:栾鹏)

python教程全解

python数据挖掘库urllib、urllib2、cookie知识全解。本文使用python2.7环境,如果需要使用python3的环境只需要按照下面的对应关系替换即可。

注意:python3.4以后中,将urllib2、urlparse、robotparser并入了urllib模块,并且修改了urllib模块,其中包含了5个子模块,每个子模块中的常用方法如下:

python3中的库			   python2中的库
urllib.error:         	 	ContentTooShortError;
							URLError;HTTPError
							
urllib.parse:           	urlparse;
							_splitparams;
							urlsplit;
							urlunparse;
							urlunsplit;
							urljoin;
							urldefrag;
							unquote_to_bytes;
							unquote;
							parse_qs;
							parse_qsl;
							unquote_plus;
							quote;
							quote_plus;
							quote_from_bytes;
							urlencode;
							to_bytes;
							unwrap;
							splittype;
							splithost;
							splituser;
							splitpasswd;
							splitport等;
							
urllib.request: 		 	urlopen; 
							install_opener; 
							urlretrieve; 
							urlcleanup; 
							request_host; 
							build_opener; 
							_parse_proxy;
							ProxyHandler;
							HTTPCookieProcessor; 
							parse_keqv_list; 
							parse_http_list; 
							_safe_gethostbyname; 
							ftperrors; 
							noheaders; 
							getproxies_environment; 
							proxy_bypass_environment; 
							_proxy_bypass_macosx_sysconf; 
							Request等
							
urllib.response: 		 	addbase; 
							addclosehook; 
							addinfo;
							addinfourl;

urllib.robotparser: 	 	RobotFileParser

在Pytho2.x中使用import urllib2——-对应的,在Python3.x中会使用import urllib.request,urllib.error。
在Pytho2.x中使用import urllib——-对应的,在Python3.x中会使用import urllib.request,urllib.error,urllib.parse。
在Pytho2.x中使用import urlparse——-对应的,在Python3.x中会使用import urllib.parse。
在Pytho2.x中使用import urlopen——-对应的,在Python3.x中会使用import urllib.request.urlopen。
在Pytho2.x中使用import urlencode——-对应的,在Python3.x中会使用import urllib.parse.urlencode。
在Pytho2.x中使用import urllib.quote——-对应的,在Python3.x中会使用import urllib.request.quote。
在Pytho2.x中使用cookielib.CookieJar——-对应的,在Python3.x中会使用http.CookieJar。
在Pytho2.x中使用urllib2.Request——-对应的,在Python3.x中会使用urllib.request.Request。

python2.7环境,以下的所有程序需要导入以下库

#coding:utf-8
import urllib
import urllib2
import cookielib

python3.6环境下,以下的所有程序需要导入以下库

#coding:utf-8
import urllib
import http.cookiejar as cookielib
  • 1.
  • 2.
  • 3.

本文使用python3.6环境调试

urllib.parse.urlparse模块,网址字符串处理

在python2中是urllib2.urlparse模块。

包含了将网址分解为6元组,将6元组分解为网址,获取指定网址上的链接所代表的绝对网址。

#urlparse()参数:网址、网络协议、是否使用片段,返回值:网络协议、服务器所在地、文件路径、可选参数、键值对、文档锚
prot_sch,net_loc,path,params,query,frag=urllib.parse.urlparse("http://www.525heart.com/index/index/index.html",None,None)
print(prot_sch,net_loc,path,params,query,frag)
#urlunparse()将6元组合并为网址字符串
urlpath=urllib.parse.urlunparse((prot_sch,net_loc,path,params,query,frag))
print(urlpath)
#urljoin()获取根域名,连接新网址
urlpath=urllib.parse.urljoin("http://www.525heart.com/index/index/index.html","../chanpin/detail.html")
print(urlpath)

urllib.request.urlretrieve模块:下载读取远程静态文件

在python2中是urllib.urlretrieve模块。

主要包含远程文件的下载,远程文件存储到本地,远程文件的读取,以及http响应的MIME头信息。

所谓的MIME头就是http协议中,数据传输都是以字节流在网线中传递,在一个文件或数据的传递起始部分都有个MIME头,用来描述本次传递的某些属性特点,如图中上面红色框中的内容。下面的红色框为传输的文件内容。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_post

#远程文件
resphonse = urllib.request.urlopen("http://www.525heart.com/index/index/index.html")                    # urlopen(url,data,timeout)打开远程文件,返回的是类文件对象,可以使用readline等文件函数
htmlcode = resphonse.readline()  #读取一行
htmlcode = resphonse.readlines()  #读取所有行
htmlcode = resphonse.read()  #读取字节流
print(resphonse.geturl())  #文件网址
print(resphonse.info())  #数据传输的MIME头文件
resphonse.close()  #关闭
urllib.request.urlretrieve("http://www.525heart.com/index/index/index.html",'index.html')                # 下载远程文件,参数为网址,可本地存储地址

urllib.request.Request模块:获取get方式请求响应流

在python2中是urllib2.Request模块。

所谓get方式,就是将请求数据序列化以后添加到网址中,打开指定网址的过程。

request = urllib.request.Request('http://www.525heart.com/web/getdiaryurl?diaryid=612')
try:
    resphonse = urllib.request.urlopen(request)                    # urlopen(url,data,timeout)打开远程文件,返回的是类文件对象,可以使用readline等文件函数
except urllib.error.URLError as e:   #本机没联网、连接不到服务器、服务器不存在
    print(e.reason)
except urllib.error.HTTPError as e:   #响应状态,响应码
    print(e.reason,e.code)
print(resphonse.read())  #读取响应数据

urllib.request.Request模块:获取post方式请求响应流

在python2中是urllib2.Request模块。

post方式就是向指定的网址发送一段数据的仿真,所以请求过程除了请求网址,还有请求数据,两个参数来创建请求对象。

postobject={"diaryid":"612"}  #将字典数据序列化
postdata = urllib.parse.urlencode(postobject)  # urlencode()字典序列化
postdata = postdata.encode('utf-8')     #将字符串编码成字节数组
url= "http://www.525heart.com/web/getdiaryurl"
request = urllib.request.Request(url,postdata)  #创建post请求对象,get请求对象:urllib2.Request(url+"?"+data)
resphonse = urllib.request.urlopen(request)   #post请求消息
print(resphonse.read())  #读取响应数据

而对于我们想要爬取的网站,如何知道需要发送什么数据呢,需要用到浏览器的监听功能了。

这里以有道翻译为例,我们希望像服务器发送需要翻译的内容,获取翻译的结果,那么应该发送什么样的内容呢。

打开有道翻译的网址http://fanyi.youdao.com/

右键检查(审查元素)-切换到Network切换到网络监听部分。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_python_02

在左侧翻译区输入你要翻译的内容如“jack”,回车后网页自动翻译,获取了翻译结果“杰克”,同时我们看到在右侧网络监听部分,已经显示出在此过程中所有的信息交互。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_post_03

我们点击第一个信息交互记录,可以进入此次交互的详情。包括http头(MIME),和请求数据的格式。我们要发送的数据格式就按照图中的格式添加就可以了。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_cookie_04

urllib.error异常

urllib.error有两个方法,URLError和HTTPError

URLError是OSError的一个子类,HTTPError是URLError的一个子类,服务器上HTTP的响应会返回一个状态码,根据这个HTTP状态码,我们可以知道我们的访问是否成功。200状态码,表示请求成功,再比如常见的404错误等。

最后值得注意的一点是,如果想用HTTPError和URLError一起捕获异常,那么需要将HTTPError放在URLError的前面,因为HTTPError是URLError的一个子类。如果URLError放在前面,出现HTTP异常会先响应URLError,这样HTTPError就捕获不到错误信息了。

urllib.request.Request模块:控制http头

在python2中是urllib2.Request模块。

很多服务器或代理服务器会查看HTTP头,进而控制网络流量,实现负载均衡,限制不正常用户的访问。所以我们要学会设置HTTP头,来保证一些访问的实现。

网站的HTTP头的设置,可以通过网页右键审查元素中查看,同上图。

HTTP头部分参数说明:

  • Upgrade-Insecure-Requests:参数为1。该指令用于让浏览器自动升级请求从http到https,用于大量包含http资源的http网页直接升级到https而不会报错。简洁的来讲,就相当于在http和https之间起的一个过渡作用。就是浏览器告诉服务器,自己支持这种操作,我能读懂你服务器发过来的上面这条信息,并且在以后发请求的时候不用http而用https;
  • User-Agent:有一些网站不喜欢被爬虫程序访问,所以会检测连接对象,如果是爬虫程序,也就是非人点击访问,它就会不让你继续访问,所以为了要让程序可以正常运行,我们需要设置一个浏览器的User-Agent;
  • Accept:浏览器可接受的MIME类型,可以根据实际情况进行设置;
  • Accept-Encoding:浏览器能够进行解码的数据编码方式,比如gzip。Servlet能够向支持gzip的浏览器返回经gzip编码的HTML页面。许多情形下这可以减少5到10倍的下载时间;
  • Accept-Language:浏览器所希望的语言种类,当服务器能够提供一种以上的语言版本时要用到;
  • Cookie:这是最重要的请求头信息之一。中文名称为“小型文本文件”或“小甜饼“,指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。定义于RFC2109。是网景公司的前雇员卢·蒙特利在1993年3月的发明。
url= "https://zhuanlan.zhihu.com/p/30488000"
postdata = urllib.parse.urlencode({"p":"30488000"})  # urlencode()字典序列化
headers={}
headers["User-Agent"]='Mozilla/4.0 (compatible;MSIE 5.5;Windows NT)'  #标明浏览器身份,有些服务器或代理服务器会来判断。这里模仿的是IE浏览器
headers["Referer"]='http://www.zhihu.com/p'  #标明文件来源,防止盗链用的
headers["Content-Type"]='text/html'  #在谁用rest接口的服务器会检测这个值,来确定内容如何解析
request = urllib.request.Request(url,headers=headers)  #headers控制请求,因为服务器会根据这个控制头决定如何响应。(resphonse.info()可以查看响应的头的信息)
resphonse = urllib.request.urlopen(request)   #post请求消息
print(resphonse.read())  #读取响应数据

常见的User Agent

1.Android

Mozilla/5.0 (Linux; Android 4.1.1; Nexus 7 Build/JRO03D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Safari/535.19

Mozilla/5.0 (Linux; U; Android 4.0.4; en-gb; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30

Mozilla/5.0 (Linux; U; Android 2.2; en-gb; GT-P1000 Build/FROYO) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1

2.Firefox

Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0

Mozilla/5.0 (Android; Mobile; rv:14.0) Gecko/14.0 Firefox/14.0

3.Google Chrome

Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.94 Safari/537.36

Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19

4.iOS

Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3

Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/3A101a Safari/419.3

上面列举了Andriod、Firefox、Google Chrome、iOS的一些User Agent,直接copy就能用。
  • 1.

urllib.request.ProxyHandler函数:设置代理服务器,防止限制IP

在python2中是urllib2.ProxyHandler函数。

控制代理服务器,防止服务器限制IP。每隔一段时间换一个代理服务器

代理服务器的ip你可以从网页中自己选择和定期更换

URL:http://www.xicidaili.com/

#控制代理服务器,防止服务器限制IP。每隔一段时间换一个代理服务器
enable_proxy = True
proxy_handler=urllib.request.ProxyHandler({"http":"61.135.217.7:80"})
null_proxy_handler = urllib.request.ProxyHandler({})
if enable_proxy:
    opener = urllib.request.build_opener(proxy_handler)
else:
    opener = urllib.request.build_opener(null_proxy_handler)
urllib.request.install_opener(opener)

使用install_opener方法之后,会将程序默认的urlopen方法替换掉。也就是说,如果使用install_opener之后,在该文件中,再次调用urlopen会使用自己创建好的opener。如果不想替换掉,只是想临时使用一下,可以使用opener.open(url),这样就不会对程序默认的urlopen有影响。

编写代码访问http://www.whatismyip.com.tw/,该网站是测试自己IP为多少的网址,服务器会返回访问者的IP。在使用代理服务器以后,访问这里查询本地IP地址的网址的返回结果也会由于代理服务器的存在而随机变化。

下面实现了一个爬虫,爬取http://www.xicidaili.com/ 网站中代理服务器的列表。

#coding:utf-8
#本实例用于获取国内高匿免费代理服务器
import urllib
from bs4 import BeautifulSoup


def getdata(html):  #从字符串中安装正则表达式获取值
    allproxy = []  # 所有的代理服务器信息
    soup = BeautifulSoup(html, 'html.parser')
    alltr = soup.find_all("tr", class_="")[1:]   #获取tr ,第一个是标题栏,去除

    for tr in alltr:
        alltd =tr.find_all('td')
        oneproxy ={
            'IP地址':alltd[1].get_text(),
            '端口号': alltd[2].get_text(),
            # '服务器地址': alltd[3].a.get_text(),
            '是否匿名': alltd[4].get_text(),
            '类型': alltd[5].get_text(),
            '速度': alltd[6].div['title'],
            '连接时间': alltd[7].div['title'],
            '存活时间': alltd[8].get_text(),
            '验证时间': alltd[9].get_text(),
        }
        allproxy.append(oneproxy)

    alltr = soup.find_all("tr", class_="odd")[1:]  # 获取tr ,第一个是标题栏,去除

    for tr in alltr:
        alltd = tr.find_all('td')
        oneproxy = {
            'IP地址': alltd[1].get_text(),
            '端口号': alltd[2].get_text(),
            # '服务器地址': alltd[3].a.get_text(),
            '是否匿名': alltd[4].get_text(),
            '类型': alltd[5].get_text(),
            '速度': alltd[6].div['title'],
            '连接时间': alltd[7].div['title'],
            '存活时间': alltd[8].get_text(),
            '验证时间': alltd[9].get_text(),
        }
        allproxy.append(oneproxy)
    return allproxy




#根据一个网址,获取该网址中符合指定正则表达式的内容
def getallproxy(url='"http://www.xicidaili.com/nn"'):
    try:
        # 构造 Request headers
        agent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER'
        headers = {  # 这个http头,根据审查元素,监听发包,可以查看
            "Host": "www.xicidaili.com",
            "Referer": "http://www.xicidaili.com/",
            'User-Agent': agent
        }
        request = urllib.request.Request(url, headers=headers)  # 创建一个请求
        response = urllib.request.urlopen(request)  # 获取响应
        html = response.read()  #读取返回html源码
        return getdata(html)
    except urllib.error.URLError as e:
        if hasattr(e,"code"):
            print(e.code)
        if hasattr(e,"reason"):
            print(e.reason)
        return []



# allproxy = getallproxy()
# print(allproxy)

有了上面的列表就可以直接从数组中随机选择一个代理服务器。

allproxy = proxy.getallproxy()  #获取一批代理服务器列表

...


oneproxy = random.choice(allproxy)   #随机选择一个代理服务器
proxy_handler = urllib.request.ProxyHandler({"http": oneproxy['IP地址']+":"+oneproxy['端口号']})  #将代理服务器ip绑定到请求对象中
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

urllib.request.HTTPCookieProcessor函数:设置cookie

在python2中是urllib2.HTTPCookieProcessor函数

cookie=cookielib.CookieJar()  #声明一个CookieJar对象实例来保存cookie
handler=urllib.request.HTTPCookieProcessor(cookie)  #利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
opener=urllib.request.build_opener(handler)  #通过handler来构建opener
resphonse=opener.open('http://blog.cn.net/luanpeng825485697/article/details/78264170')  #cookie是由服务器来设定的存储在客户端,客户端每次讲cookie发送给服务器来携带数据
for item in cookie:
    print('Name='+item.name)
    print('Value='+item.value)

cookie模块:将cookie写入文件

filename='cookie.txt'
cookie=cookielib.MozillaCookieJar(filename)  #声明一个MozillaCookieJar对象实例来保存cookie,之后写入文件
handler=urllib.request.HTTPCookieProcessor(cookie) #利用urllib2库的HTTPCookieProcessor对象来创建cookie处理器
opener = urllib.request.build_opener(handler)  #通过handler来构建opener
resphonse=opener.open('http://blog.cs.net/luanpeng825485697/article/details/78264170')  #创建一个请求,原理同urllib2的urlopen
cookie.save(ignore_discard=True, ignore_expires=True) #保存cookie文件

ookie.save的参数说明:

ignore_discard的意思是即使cookies将被丢弃也将它保存下来;
ignore_expires的意思是如果在该文件中cookies已经存在,则覆盖原文件写入。

在这里,我们将这两个全部设置为True。

运行之后,cookies将被保存到cookie.txt文件中。我们可以查看自己查看下cookie.txt这个文件的内容。

cookie模块:从文件中读取cookie

cookie=cookielib.MozillaCookieJar()  #声明一个MozillaCookieJar对象实例
cookie.load("cookie.txt",ignore_discard=True, ignore_expires=True) #从文件中读取cookie内容到变量
req=urllib.request.Request("http://blog.cs.net/luanpeng825485697/article/details/78264170") #创建请求的request
opener=urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie))  #利用urllib2的build_opener方法创建一个opener
response=opener.open(req)  #使用包含了指定cookie的opener发起请求
print(resphonse.read())  #打印响应

cookie模块:利用cookie模拟登陆

创建一个带有cookie的opener,在访问登录的URL时,将登录后的cookie保存下来,然后利用这个cookie来访问其他网址

现在很多网站需要登陆后才能访问,如知乎,地址https://www.zhihu.com

在没有登陆时访问这个网址,只能出现登陆界面

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_cookie_05

如果登陆以后,在访问这个网址,就会出现文章列表。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_python_06

这是因为访问此地址,知乎服务器会查询请求cookie,如果请求cookie没有用户信息,就证明没有登陆,就会返回登陆界面,如果有cookie信息就会返回文章列表界面,同时包含用户的其他信息。所以首先需要让自己的请求中能带有包含自己信息的cookie。这一步通过登陆来实现。

在登陆界面,通过post将用户账号密码发送给服务器,服务器会将用户信息以cookie的形式返回给用户,用户在下次请求时,就会自动将这个cookie添加到请求http头中,这样下次再访问时,服务器就知道这个用户已经登陆了。

所以在使用cookie模拟登陆,需要两步,第一步是获取cookie,第二步是将cookie添加到http中,模拟用户已经存在的方式请求文章。

第一步,登陆获取cookie(通过post向指定网址发送数据,获取返回cookie即可)

对于发送数据需要添加的post数据和http头可以通过审查元素监听。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_urllib_07

由于浏览器的监听在跳转到新的网页就会清空重新监听,所以在登陆到文章列表页面,浏览先监听了登陆页面的数据,后清空监听记录,重新监听文章列表页面的数据。不能及时记录下载登陆界面发送的请求数据,所以建议使用fiddle监听数据。

不过也不是没有办法,监听区红色的记录开关按钮,可以及时的阻止监听。

这里在点击登陆后,我立刻点击了浏览监听中的红色记录按钮,拦截了登陆界面的数据。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_python_08

可以看到需要发送的数据除了phone_num手机号、password密码、captcha_type验证码类型(选择不同的登陆方式需要发送的数据类型可以不同),还有一个_xsrf,这个字段是在登陆网页源代码中

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_数据挖掘_09

所以在发送post前还要获取网页源代码解析这个字段。

# -*- coding: UTF-8 -*-
import requests
#import http.cookiejar as cookielib
from bs4 import BeautifulSoup
import cookielib

#post登陆信息,生成cookie文件

session = requests.Session()
session.cookies = cookielib.LWPCookieJar("cookie")
agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/5.1.2.3000 Chrome/55.0.2883.75 Safari/537.36'
headers = {   #这个http头,根据审查元素,监听发包,可以查看
    "Host": "www.zhihu.com",
    "Origin":"https://www.zhihu.com/",
    "Referer":"http://www.zhihu.com/",
    'User-Agent':agent
}

postdata = {
    'phone_num': '18158208197',  #填写手机号
    'password': '19910101a', #填写密码
    'captcha_type':'cn',
    
}
response = session.get("https://www.zhihu.com", headers=headers)
soup = BeautifulSoup(response.content, "html.parser")
xsrf = soup.find('input', attrs={"name": "_xsrf"}).get("value")  #解析_xsrf字段
postdata['_xsrf'] =xsrf  #因为知乎登陆需要这个字段的消息
result = session.post('http://www.zhihu.com/login/email', data=postdata, headers=headers)  #发送数据
session.cookies.save(ignore_discard=True, ignore_expires=True)  #及时登陆失败也会生成cookie,不过这个cookie不能支持后面的模拟登陆状态

第二步、通过获取的cookie,模拟已经登陆的状态。这样再次访问知乎网址,就能获取文章了。

访问文章需要的http头可以通过审查元素查看。

python网络爬虫系列教程——python中urllib、urllib2、cookie模块应用全解_cookie_10

# -*- coding: UTF-8 -*-
import requests
#import http.cookiejar
from bs4 import BeautifulSoup
import cookielib

#加载cookie文件模拟登陆

session = requests.session()
session.cookies = cookielib.LWPCookieJar(filename='cookie')
try:
    session.cookies.load(ignore_discard=True)
except:
       print("Cookie 未能加载")
def isLogin():
    url = "https://www.zhihu.com/"
    login_code = session.get(url, headers=headers, allow_redirects=False).status_code
    if login_code == 200:
        return True
    else:
        return False
if __name__ == '__main__':
    agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Maxthon/5.1.2.3000 Chrome/55.0.2883.75 Safari/537.36'
    headers = {
        "Host": "www.zhihu.com",
        "Origin": "https://www.zhihu.com/",
        "Referer": "http://www.zhihu.com/",
        'User-Agent': agent,
    }
    if isLogin():
        print('您已经登录')