Python爬虫基本库的使用


已写章节

第一章 网络爬虫入门
第二章 基本库的使用
第三章 解析库的使用
第四章 数据存储
第五章 动态网页的抓取


第二章 基本库的使用



2.1 urllib库的使用(非重点)


urllib的官方文档

urllib是Python中自带的HTTP请求库,也就是说不用额外安装就可以使用,它包含如下四个模块:

  • requests:基本的HTTP请求模块,可以模拟发送请求
  • error:异常处理模块
  • parse:一个工具模块,提供了许多URL处理方法,比如拆分、解析、合并等。
  • robotparser:它主要用来识别网站的robots.txt文件,让后判断哪些内容可以爬取,哪些不能爬取,用得比较少。

2.1.1 request模块



  • 发送请求
# 2.1 使用urllib库中的request模块发送一个请求的例子
import urllib.request

response = urllib.request.urlopen('http://www.baidu.com')
print(response.read().decode('utf-8'))

使用request.urlopen()来向百度首页发起请求,返回的是http.client.HTTPResponse对象,这个对象主要包含read()readinto()getheader(name)getheaders()fileno()等方法,以及msgversionstatusreasondebuglevelclosed等属性。将返回的HTML代码以utf-8的编码方式读取并打印出来。上面的代码执行后将返回百度的主页的HTML代码。

我运行的效果如下:

<!DOCTYPE html><!--STATUS OK-->


    <html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="referrer"><meta name="theme-color" content="#2932e1"><meta name="description" content="全球最大的中文搜索引擎、致力于让网民更便捷地获取
信息,找到所求。百度超过千亿的中文网页数据库,可以瞬间找到相关的搜索结果。">
后面省略无数字......


接下来再看这个例子:

# 2.2 使用urllib中的request模块发起一个请求并获取response对象中的信息的例子
import urllib.request

response = urllib.request.urlopen("http://www.python.org")
print(response.read().decode('utf-8')[:100]) # 截取返回的html代码的前100个字符的信息
print("response的类型为:" + str(type(response)))
print("response的状态为:" + str(response.status))
print("response的响应的头信息:" + str(response.getheaders()))
print("response的响应头中的Server值为:" + str(response.getheader('Server')))

上面的代码使用urlopen()方法向指定的链接发起了一个请求,得到一个HTTPResponse对象,然后调用HTTPResponse的方法和属性来获取请求的状态、请求头信息等

下面是我的执行结果:

<!doctype html>
<!--[if lt IE 7]>   <html class="no-js ie6 lt-ie7 lt-ie8 lt-ie9">   <![endif]-->
<!-
response的类型为:<class 'http.client.HTTPResponse'>
response的状态为:200
response的响应的头信息:[('Connection', 'close'), ('Content-Length', '50890'), ('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'DENY'), ('Via', '1.1 vegur, 1.1 varnish, 1.1 varnish'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 17 May 2021 08:59:57 GMT'), ('Age', '1660'), ('X-Served-By', 'cache-bwi5163-BWI, cache-hkg17920-HKG'), ('X-Cache', 'HIT, HIT'), ('X-Cache-Hits', '1, 3886'), ('X-Timer', 'S1621241997.260514,VS0,VE0'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
response的响应头中的Server值为:nginx


  • data参数

data参数是可选的,该参数是bytes类型,需要使用bytes()方法将字典转化为字节类型,并且,该参数只能在POST请求中使用。

# 2.3 data参数的使用
import urllib.request

# 使用urllib中的parse模块中的urlencode方法来将字典转化为字节类型,编码方式为utf-8
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())

这次我们请求的是http://httpbin.org/post这个网址,这个网址可以提供http请求测试,它可以返回请求的一些信息,其中包括我们传递的data参数。



  • timeout参数

timeout参数用来设置超时时间,单位为秒,意思是如果请求超出了设置的这个时间,还没有得到响应,就会抛出异常。

# 2.4 timeout参数的使用
import urllib.request

response = urllib.request.urlopen('http://www.baidu.com', timeout=1)
print(response.read())

运行结果就不展示了。



  • 其他参数

除了data参数和timeout参数外,还有context参数,它必须是ssl.SSLContext类型,用来指定SSL设置



Request类

urlopen()可以实现基本的请求的发起,但这不能构造一个完整的请求,如果要在请求中加入Headers等信息,就可以利用更强大的Request类来构建。

# 2.5 Request类的使用
import urllib.request

request = urllib.request.Request('https://python.org')
print(type(request))
response = urllib.request.urlopen(request)   # 传入的是Request对象
print(response.read().decode('utf-8'))

request的构造方法:

Requests(url, data, headers, origin_host, unverifiablem, method)

  • url:请求的url链接
  • data:必须为字节流(bytes)类型
  • headers:请求头信息
  • origin_req_host:请求方的host名称或者IP地址
  • unverifiable:表示这个请求是否是无法验证的,默认为False。
  • method:指示请求使用的方法,比如:GET、POST、PUT等

下面是例子:

# 2.6 Request类的使用
from urllib import request, parse

url = "http://httpbin.org/get"
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
}
dict = {
    'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='GET')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

我们依然请求的是测试网址http://httpbin.org/get,它会返回我们发起的请求信息,下面是我的运行结果:

{
  "args": {}, 
  "headers": {
    "Accept-Encoding": "identity", 
    "Content-Length": "11", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)", 
    "X-Amzn-Trace-Id": "Root=1-60a236ed-01f68c862b09c8934983ae80"
  }, 
  "origin": "221.176.140.213", 
  "url": "http://httpbin.org/get"
}

从结果中,我们可以看到我们发起的请求中包含了我们自己设置的User-Agent,Host和我们请求中包含的数据 ‘name’: ‘Germey’。



2.1.2 error模块

urllib中的error模块定义了由request模块产生的异常,如果出现了问题,request模块就会抛出error模块中的异常。

下面介绍其中用得比较多的两个异常:URLErrorHTTPError



  • URLError

    URLError类是error异常模块的基类,由request模块产生的异常都可以通过捕获这个异常来处理。

    # 2.7 URLError的使用例子
    from urllib import request, error
    
    # 打开一个不存在的网页
    try:
        response = request.urlopen('https://casdfasf.com/index.htm')
    except error.URLError as e:
        print(e.reason)
    

    运行结果:

    [Errno 11001] getaddrinfo failed
    


  • HTTPError

    它是URLError的子类,专门用来处理HTTP请求错误,比如认证请求失败等。它有如下3个属性:

    1. code:返回HTTP状态码
    2. reason:返回错误的原因
    3. headers:返回请求头
    # 2.8 HTTPError对象的属性
    from urllib import request, error
    
    try:
        response = request.urlopen('https://cuiqingcai.com/index.htm')
    except error.HTTPError as e:
        print(e.reason, e.code, e.headers, sep='\n')
    

    运行结果:

    Not Found
    404
    Server: GitHub.com
    Date: Tue, 16 Feb 2021 03:01:45 GMT
    Content-Type: text/html; charset=utf-8
    X-NWS-UUID-VERIFY: 8e28a376520626e0b40a8367b1c3ef01
    Access-Control-Allow-Origin: *
    ETag: "6026a4f6-c62c"
    x-proxy-cache: MISS
    X-GitHub-Request-Id: 0D4A:288A:10EE94:125FAD:602B33C2
    Accept-Ranges: bytes
    Age: 471
    Via: 1.1 varnish
    X-Served-By: cache-tyo11941-TYO
    X-Cache: HIT
    X-Cache-Hits: 0
    X-Timer: S1613444506.169026,VS0,VE0
    Vary: Accept-Encoding
    X-Fastly-Request-ID: 9799b7e3df8bdc203561b19afc32bb5803c1f03c
    X-Daa-Tunnel: hop_count=2
    X-Cache-Lookup: Hit From Upstream
    X-Cache-Lookup: Hit From Inner Cluster
    Content-Length: 50732
    X-NWS-LOG-UUID: 5426589989384885430
    Connection: close
    X-Cache-Lookup: Cache Miss
    


2.1.3 parse模块

parse模块是用来处理url的模块,它可以实现对url各部分的抽取、合并以及连接装换等。

下面介绍parse模块中常用的几个方法:


  • urlparse()

    实现url的识别和分段

    # 2.9 urllib库中parse模块中urlparse()方法的使用
    from urllib.parse import urlparse
    
    result = urlparse('http://www.biadu.com/index.html;user?id=5#comment')
    print(type(result), result)
    
    result1 = urlparse('www.biadu.com/index.html;user?id=5#comment', scheme='https')
    print(type(result1), result1)
    
    result2 = urlparse('http://www.baidu.com/index.html#comment', allow_fragments=False)
    print(type(result2), result2)
    
    result3 = urlparse('http://www.baidu.com/index.html#comment', allow_fragments=False)
    print(result3.scheme, result3[0], result3.netloc, result3[1], sep="\n")
    

    运行结果:

    <class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.biadu.com', path='/index.html', params='user', query='id=5', fragment='comment')
    <class 'urllib.parse.ParseResult'> ParseResult(scheme='https', netloc='', path='www.biadu.com/index.html', params='user', query='id=5', fragment='comment')
    <class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')
    http
    http
    www.baidu.com
    www.baidu.com
    

    可以看到,urlparse()方法将url解析为6部分,返回的是一个ParseResult对象,这6部分是:

    1. scheme:😕/前面的部分,指协议
    2. netloc:第一个/的前面部分,指域名
    3. path:第一个/后面的部分,指访问路径
    4. params:;后面的部分,指参数
    5. query:问号后面的内容,指查询条件
    6. fragment:#号后面的内容,值锚点

    所以,可以得出一个标准的链接格式:

    scheme://netloc/path;params?query#fragment

    一个标准的URL都会符合这个规则,我们就可以使用urlparse()这个方法来将它拆分开来。

    urlparse()方法还包含三个参数:

    urlstring:必选项,即待解析的URL

    scheme:默认协议,假如这个链接没有带协议信息,会将这个作为默认的协议。

    allow_fragments:即是否忽略fragment。如果它被设置为false,fragment就会被忽略,它会被解析为path、params或者query的一部分,而fragment部分为空



  • urlunparse()

    它和urlparse()方法相反,它接收的参数是一个可迭代对象(常见的有列表、数组、集合),它的长度必须为6。

    # 2.10 parse模块中的urlparse方法的使用
    from urllib.parse import urlunparse
    
    data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
    print(urlunparse(data))
    

    上面的例子中传入的是一个包含6个元素的列表,当然可以是其他类型,如元组,只需要这个可迭代对象的长度为6个就可以了。



  • urlsplit()

    和urlparse()方法相似,只不过它不再单独解析params这一部分,而将params加入到path中,只返回5个结果

    # 2.11 parse模块中的urlsplit方法的使用
    from urllib.parse import urlsplit
    
    result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
    print(result)
    

    运行结果:

    SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')
    


  • urlunsplit()

    和urlsplit()方法相反,传入的参数也是一个可迭代的对象,例如列表、元组等,长度必须为5。

    # 2.12 parse模块中的urlunsplit方法的使用
    from urllib.parse import urlunsplit
    
    data = ['http', 'www.baidu.com', 'index.html', 'a=6', 'comment']
    print(urlunsplit(data))
    

    运行结果:

    http://www.baidu.com/index.html?a=6#comment
    


  • urljoin()

    该方法接收两个参数,一个是base_url,另外一个是新的url,该方法会分析新url中的scheme、netloc、path三部分的内容是否存在,如果某个不存在,就用base_url中的来替换。



  • urlencode()

    将字典类型转换为url中的参数

    # 2.13 parse模块中的urlencode方法的使用
    from urllib.parse import urlencode
    params = {
        'name': 'germey',
        'age': '22'
    }
    base_url = 'http://www.baidu.com?'
    url = base_url + urlencode(params)
    print(url)
    

    运行结果:

    http://www.baidu.com?name=germey&age=22
    


  • parse_qs()、parse_qsl()

    parse_qs:反序列化,将get请求参数转化为字典

    parse_qsl:反序列化,将get参数转化为元组组成的字典

    # 2.14 parse模块中parse_qs和parse_qsl方法的使用
    from urllib.parse import parse_qs, parse_qsl
    
    # 反序列化,将参数转化为字典类型
    query = 'name=germey&age=22'
    print(parse_qs(query))
    
    # 反序列化,将参数转化为元组组成的列表
    print(parse_qsl(query))
    

    运行结果:

    {'name': ['germey'], 'age': ['22']}
    [('name', 'germey'), ('age', '22')]
    


  • quote()、unquote()

    quote:将内容转化为URL编码的格式,URL中带有中文参数时,有可能会导致乱码的问题,用这个方法就可以将中文字符转化为URL编码。

    unquote:和quote()方法相反,将URL解码

    # 2.15 parse模块中quote()方法和unquote()方法的使用
    from urllib.parse import quote, unquote
    
    keyword = '书包'
    url = 'https://www.baidu.com/?wd=' + quote(keyword)
    print(url)
    
    url = 'https://www.baidu.com/?wd=%E5%A3%81%E7%BA%B8'
    print(unquote(url))
    

    运行结果:

    https://www.baidu.com/?wd=%E4%B9%A6%E5%8C%85
    https://www.baidu.com/?wd=壁纸
    

urllib中的robotparse模块可以实现robots协议的分析,用得不多,这里就不再介绍。



2.2 requests库的使用(重点)

学习爬虫,最基础的便是模拟浏览器向服务器发出请求。

requests文档


2.2.1. requests库的介绍

利用Python现有的库可以非常方便的实现网络请求的模拟,常见的有urllib、requests等。requests模块是Python原生的一款基于网络请求的模块,功能非常强大,效率极高。

作用:模拟浏览器发起请求


2.2.2 requests库的安装

requests库是Python的第三方库,需要额外安装,在cmd中输入以下代码使用pip安装:

pip install requests


2.2.3 requests库的使用

要检查是否已经正确安装requests库,在编辑器或Python自带的IDLE中导入requests库:

import requests

执行后没有发生错误就说明你已经正确安装了requests库。

也可以在cmd命令行窗口中输入:

pip list

后按回车,就会显示所有pip已安装的第三方库,在列表中看到request库就表明成功安装requests库。


下面介绍Requests库的7个主要方法:

方法介绍
requests.request()构造一个请求,支撑以下的各种基础方法,不常用
requests.get()获取HTML网页的主要方法,对应于HTTP的GET,常用
requests.head()获取HTML网页头信息的方法,对应于HTTP的
requests.post()向HTML页面提交POST请求的方法,对应于HTTP的POST
requests.put()向HTM页面提交PUT请求的方法,对应于HTTP的PUT
requests.patch()向HTML页面提交局部修改请求,对应于HTTP的PATCH
requests.delete()向HTML页面提交删除请求,对应于HTTP的DELETE

2.2.3.1 requests.get()方法

requests.get(url, params=None, **kwargs)

  • url : 要获取页面的url链接,必选
  • params :url中的额外参数,字典或字节流格式,可选
  • **kwargs : 12个控制访问的参数,可选

我们在浏览器中输入一个url后按下enter,其实是发起一个get请求。同样,使用requests库发起一个get请求,可以使用requests库下的get()方法,requests.get()方法可以构造一个向服务器请求资源的Request对象并返回一个包含服务器资源的Response对象。request对象和response对象是requests库中的2个重要对象,response对象包含服务器返回的所有信息,也包含请求的request信息,下面是response对象的属性:

属性说明
r.status_codeHTTP请求的返回状态,200表示成功,404表示失败
r.textHTTP响应内容的字符串形式,即url对应的页面内容
r.encoding从HTTP header中猜测的响应内容编码方式,如果header中不存在charset,则认为编码为ISO-8859-1,r.text根据r.encoding显示网页内容
r.apparent_encoding从内容中分析出的响应内容的编码方式(备用编码方式,一般比较准确)
r.contentHTTP响应内容的二进制形式

下面是一个使用requests.get()方法访问百度并打印出返回的response对象的各种属性的例子:

import requests

# 2.16 使用requests.get()方法发送一个get()请求
r = requests.get("https://www.baidu.com")
print("r的状态码为:", r.status_code)
print("r的内容为:", r.text)
print("从r的header中推测的响应内容的编码方式为:", r.encoding)
print("从r的内容中分析出来的响应内容编码方式为:", r.apparent_encoding)
print("r内容的二进制形式为:", r.content)


2.2.3.2 requests.request()方法

requests.request(method, url, **kwargs)

  • method : 请求方式,对应get/put/post/head/patch/delete/options7种
  • url : 要获取页面的url链接
  • **kwargs : 控制访问的参数,共13个

**kwargs:控制访问的参数,都为可选项

控制访问参数说明
params字典或字节序列,作为参数增加到url中
data字典、字节序列或文件对象,作为request的内容
jsonJSON格式的数据,作为request的内容
headers字典,HTTP定制头
cookies字典或cookieJar,Request中的cookie
auth元组,支持HTTP认证功能
files字典类型,传输文件
timeout设置超时时间,秒为单位
proxies字典类型,设置代理服务器,可以增加登录认证
allow_redirectsTrue/False,默认为True,重定向开关
streamTrue/False,默认为True,获取内容立即下载开关
verifyTrue/False,默认为True,认证SSL证书开关
cert本地SSL证书路径

下面只介绍几个常用的参数。


params:字典或字节序列,作为参数增加到url中

import requests

# 2.17 请求参数params参数的使用
kv = {"key1": "value1", "key2": "value2"}
r = requests.request('GET', 'http://httpbin.org/get', params=kv)
# 也可写成: r = requests.get('http://httpbin.org/get', params=kv)
print(r.url)
print(r.text)

如果向网站http://httpbin.org/get发起一个get请求,该网站会将你的请求头的信息返回回来。

print(r.url)将输出:

http://httpbin.org/get?key1=value1&key2=value2,

可以看到,已经将字典作为参数增加到url中了!

同时上面的代码print(r.text)也将请求放回的信息也打印出来了:

{
“args”: {
“key1”: “value1”,
“key2”: “value2”
},
“headers”: {
“Accept”: “/”,
“Accept-Encoding”: “gzip, deflate”,
“Host”: “httpbin.org”,
“User-Agent”: “python-requests/2.24.0”,
“X-Amzn-Trace-Id”: “Root=1-600f8cf4-6af557655f1c1a771135e7fb”
},
“origin”: “117.150.137.110”,
“url”: “http://httpbin.org/get?key1=value1&key2=value2”
}

上面就是我们发起的请求的相关信息,有请求头、发起请求的浏览器、参数等。


data:字典、字节序列或文件对象,作为request的内容

import requests

# 2.18 请求参数data的使用
data = 'data'
r = requests.request('POST', 'http://httpbin.org/post', data=data)
# 也可写成: r = requests.post('http://httpbin.org/post', data=data)
# 这发起的是POST请求,可以看3.4
print(r.text)

我执行后的结果:

{
“args”: {},
“data”: “data”,
“files”: {},
“form”: {},
“headers”: {
“Accept”: “/”,
“Accept-Encoding”: “gzip, deflate”,
“Content-Length”: “4”,
“Host”: “httpbin.org”,
“User-Agent”: “python-requests/2.24.0”,
“X-Amzn-Trace-Id”: “Root=1-600f8f3d-46564aa85b91258f2d1c7511”
},
“json”: null,
“origin”: “117.150.137.110”,
“url”: “http://httpbin.org/post”
}

可以看到,"data"已经作为request的内容了。


json:JSON格式的数据,作为request的内容

import requests

# 2.19 请求参数json的使用
kv = {'key1':'value1'}
r = requests.request('POST', 'http://httpbin.org/post', json=kv)
# 也可写成:r = requests.post('http://httpbin.org/post', json=kv)
print(r.text)

我执行后的结果:

{
“args”: {},
“data”: “{“key1”: “value1”}”,
“files”: {},
“form”: {},
“headers”: {
“Accept”: “/”,
“Accept-Encoding”: “gzip, deflate”,
“Content-Length”: “18”,
“Content-Type”: “application/json”,
“Host”: “httpbin.org”,
“User-Agent”: “python-requests/2.24.0”,
“X-Amzn-Trace-Id”: “Root=1-600f8f00-2419855b4f772b4851c12cdd”
},
“json”: {
“key1”: “value1”
},
“origin”: “117.150.137.110”,
“url”: “http://httpbin.org/post”
}


headers:字典、HTTP定制头

请求头Headers提供了关于请求、响应或其他发送实体的信息。对于爬虫而言,请求头非常重要,有很多网站会通过检查请求头来判断请求是不是爬虫发起的。

那如何找到正确的Headers呢?

在Chrome中或其他浏览器打开要请求的网页,右击网页任意处,在弹出的菜单中单击“检查"选项,在这里插入图片描述
点击Network,点击下面的资源列表中的任意一个,我点击的是第一个,在右边弹出的界面中找到Requests Headers,就可以看到Requests Headers中的详细信息:
在这里插入图片描述
我们将Requests Headers中的user-agent对应的信息复制下来,用在下面的代码中:

import requests

# 2.20 请求参数headers的使用
headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36'}
r = requests.request('POST', 'http://httpbin.org/post',headers=headers)
# 也可写成: r = requests.post('http://httpbin.org/post',headers=headers)
print(r.text)

我的运行结果:

{
“args”: {},
“data”: “”,
“files”: {},
“form”: {},
“headers”: {
“Accept”: “/”,
“Accept-Encoding”: “gzip, deflate”,
“Content-Length”: “0”,
“Host”: “httpbin.org”,
“User-Agent”: “Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.104 Safari/537.36”,
“X-Amzn-Trace-Id”: “Root=1-600f9f2f-016953bc4635251a20212054”
},
“json”: null,
“origin”: “117.150.137.110”,
“url”: “http://httpbin.org/post”
}

可以看到,我们的请求头中的user-Agent已经变成我们自己构造的了!


timeout:设置超时时间,秒为单位

import requests

# 2.21 请求参数timeout的使用
r = requests.request('GET', 'http://www.baidu.com', timeout=10)
# 也可写成: r = requests.request('http://www.baidu.com', timeout=10)

proxies:字典类型,设定访问代理服务器,可以增加代理验证

import requests

# 2.22 请求参数proxies的使用
pxs = {
    'http': 'http://user:pass@10.11.1:12344'
    'https': 'https://10.10.10.1.12344'
}
r = requests.request('GET', 'http://www.baidu.com', proxies=pxs)
# 也可写成: r = requests.get('http://www.baidu.com', proxies=pxs)

cookies: 字典或cookieJar,Request中的cookie

首先看一下获取cookie的过程:

import requests

# 2.23 请求参数cookies的使用
r = requests.request('GET', 'https://www.baidu.com')
# 也可写成: r = requests.get('https://www.baidu.com')
print(r.cookies)

for key, value in r.cookies.items():
    print(key + '=' + value)

运行结果如下:

<RequestsCookieJar[<Cookie BDORZ=27315 for .baidu.com/>]>
BDORZ=27315

第一行是直接打印的cookie信息,我们发现它是RequestCookieJar类型。第二行是遍历的Cookie的信息。

我们也可以使用Cookie来保持登录状态,以登录百度为例,首先,登录百度,让后用上面我教你的方法来获取Requests Headers中的user-agent和cookie的信息,将它们设置到Headers中,然后发送请求:

import requests

# 2.24 使用cookies的实例
headers = {
    'Cookie': 'BIDUPSID=A41FB9F583DE46FF509B8F9443183F5C;\
              PSTM=1604237803; BAIDUID=A41FB9F583DE46FF6179FBA5503669E3:FG=1;\
              BDUSS=jdRb3ZMQTR5OX5XYTd1c0J3eUVSWGVlRVgxZ0VlMjRFR3dGMkZZMDlMNWR\
              3eWRnRVFBQUFBJCQAAAAAABAAAAEAAACzkIz7vfDP~rarMzIxAAAAAAAAAAAAAAAAAA\
              AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF02AGBdNgBgW;\
               BDORZ=B490B5EBF6F3CD402E515D22BCDA1598; H_PS_PSSID=33425_33437_33344_\
               31253_33284_33398_33459_26350; delPer=0; PSINO=3; BD_HOME=1; BD_UPN=123\
               14753; BA_HECTOR=2k808k8h0000a18g001g0v9e80r',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, \
                  like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50'
}
r = requests.request('GET', "https://www.baidu.com", headers=headers)
# 也可以写成  r = requests.get("https://www.baidu.com", headers=headers)
print(r.text)

这样,我们就实现了用Python模拟登录到百度了,结果中包含我们只有登录百度后才能查看的信息。



2.2.3.3 处理requests库的异常

在上面的代码中,r=requests.get(“https://www.baidu.com”),并不是在任何时候都会成功,如果链接打错或访问的链接不存在,则会产生异常。下面列出了常见的异常:

异常说明
requests.ConnectionError网络链接错误,入DNS查询失败,拒绝链接等
requests.HTTPErrorHTTP错误异常
requests.URLRequiredURL缺失异常
requests.TooManyRedirects超过最大重定向次数,产生重定向异常
requests.ConnectionTimeout链接远程服务器超时异常
requests.Timeout请求URL超时,产生超时异常

处理异常,可以使用requests.raise_for_status()方法,该方法在其内部判断r.status_code是否等于200,不需要增加额外的if语句:

import requests

# 2.25 处理发送请求过程中的异常
try:
    # 下面的链接错了,将打印输出"参数异常"
    r = requests.get("https://www.baidu.co")
    r.raise_for_status()
    r.encoding = r.apparent_encoding
    print(r.text)
except:
    print("产生异常")


2.2.3.4 发送POST请求

除了GET请求外,有时还需要发送一些编码为表单形式的数据,如在登录的时候请求就为POST,因为如果使用GET请求,密码就会显示在URL中,这是非常不安全的。如果要实现POST请求,其实在上面的介绍requests.request( )方法时就已经实现了,你也可以看看下面用requests.post()方法实现的代码,对比后发现,requests.post()方法其实就是将requests.request(‘POST’, …)方法给包装起来了,这也是为什么说requests.request()方法是实现其他方法的基础了,上面的例子中也建议大家不用requests.request()方法。

import requests

# 2.26 发送POST请求
key_dict = {'key1':'value1', 'key2': 'value2'}
r = requests.post('http://httpbin.org/post', data=key_dict)
# 也可写成: r = requests.request('POST', 'http://httpbin.org/post', data=key_dict)
print(r.text)

按照上面的requests.request()方法的13个访问参数,你就会将其应用到requests.get()或requests.post()方法中了吧!在来看一个例子:

import requests

r = requests.get('http://www.baidu.con', timeout=3)
print(r.text)


2.2.3.5 requests库的使用实例:TOP250电影数据、抓取二进制数据

打开豆瓣电影TOP250的网站,右键鼠标,使用“检查"功能查看该网页的请求头:
在这里插入图片描述
按照下面提取请求头中的重要信息:

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50',
    'Host': 'movie.douban.com'
}

使用这个headers构造请求头并爬取前25个电影的名称,提取部分的代码大家看不懂没关系,大家只要看懂请求发起代码的实现:

import requests
from bs4 import BeautifulSoup

# 2.27 发送get请求爬取豆瓣电影的前25个电影
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.96 Safari/537.36 Edg/88.0.705.50',
    'Host': 'movie.douban.com'
}
r = requests.get('https://movie.douban.com/top250', headers=headers, timeout=10)
try:
    r.raise_for_status()
    soup = BeautifulSoup(r.text, 'html.parser')
    movies = soup.find_all('div', {'class': 'hd'})
    for each in movies:
        print(each.a.span.text.strip())
except:
    print("error")

执行后将输出:

肖申克的救赎
霸王别姬
阿甘正传
这个杀手不太冷

下面是一个抓取favicon.ico图标的例子:

import requests

# 2.28 发送get请求爬取图片
r = requests.get('https://github.com/favicon.ico')
with open('favicon.ico', 'wb') as f:
    f.write(r.content)

运行结束后,文件夹中将出现名为favicon.ico的图标。上面的代码先使用get方法发起一个get请求,让后将请求返回的内容以wb(二进制)写入favicon.ico文件中。


2.3 正则表达式(重点)



2.3.1 正则表达式的介绍

本节中,我们看一下正则表达式的相关用法。正则表达式是处理字符串的强大工具,它有自己特定的语法结构,有了它,实现字符串的检索、替换、匹配验证都不在话下。

正则表达式是用来简介表达一组字符串的表达式,正则表达式是一种通用的字符串表达框架。正则表达式可以用来判断某字符串的特征归属。

在爬虫中,我们使用它来从HTML中提取想要的信息就非常方便了。

例如:正则表达式:

p(Y|YT|YTH|YTHO)?N

它可以表示:

  • ‘PN’
  • ‘PYN’
  • ‘PYTN’
  • ‘PYTHN’
  • ‘PYTHON’

这5个字符串。

正则表达式语法由字符和操作符构成,上面的例子中的() | ? 都是操作符,其他的是字符。


下面是正则表达式的常用的操作符:

操作符说明实例
.表示任何单个字符,除了换行符
[ ]字符集,对单个字符给出取值范围[abc]表示a、b、c,[a-z]表示a到z单个字符
[^ ]非字符集,对单个字符给出排除范围[^abc]表示非a非b非c的单个字符
*前一个字符0次或无限次扩展abc*表示ab、abc、abcc、abccc等
+前一个字符1次或无限次扩展abc+表示abc、abcc、abccc等
前一个字符0次或1次扩展abc?表示ab、abc
|左右表达式中任意一个abc|def表示abc、def
{m}扩展前一个字符m次ab{2}c表示abbc
{m, n}扩展前一个字符m至n次(含n)ab{1,2}c表示abc、abbc
^匹配字符串开头^abc表示abc且在一个字符串的开头
$匹配字符串结尾abc$表示abc且在一个字符的结尾
( )分组标记,内部只能使用|操作符(abc)表示abc,(abc|def)表示abc、def
\d数字,等价于[0-9]
\D匹配非数字
\w匹配非特殊字符,即a-z、A-Z、0-9、_、汉字
\W匹配特殊字符,即非字母、非数字、非汉字、非_
\n匹配一个换行符
\s匹配空白
\S匹配非空白

正则表达式语法实例:

正则表达式对应字符串
p(Y|YT|YTH|YTHO)?N‘PN’,‘PYN’,‘PYTN’,‘PYTHN’,‘PYTHON’
PYTHON+‘PYTHON’,‘PYTHONN’,‘PYTHONNN’ …
PY[TH]ON‘PYTON’,‘PYHON’
PY[^TH]?ON‘PYON’,‘PYaON’,‘PYbON’,‘PYcON’ …
PY{ : 3}N‘PN’,‘PYN’,‘PYYN’、‘PYYYN’

经典正则表达式实例:

正则表达式对应字符串
^[A-Za-z]+$由26个字母组成的字符串
^[A-Za-z0-9]+$由26个字母和数字组成的字符串
^\d+$整数形式的字符串
^[0-9]*[1-9][0-9]*$正整数形式的字符串
[1-9]\d{5}中国境内的邮政编码,6位
[\u4e00-\u9fa5]匹配中文字符
\d{3}-\d{8}|\d{4}-\d{7}国内电话号码,010-68913536
\d+.\d+.\d+.\d+. 或 \d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}匹配IP地址的字符串

其实正则表达式不是Python仅有的,它也出现在其他的编程语言中,只不过语法可能有一点不同。Python中的re库提供了整个正则表达式的实现,下面就来介绍re库。


2.3.2 Re库


2.3.2.1 Re库的介绍

Re库是Python的标准库,主要用于字符串匹配。

re文档

python中的re库文档


2.3.2.2 Re库的使用

要使用Re库,必须先安装re库:

pip install re


re库的主要功能函数

函数说明
re.match()从一个字符串的开始位置起匹配正则表达式
re.search()在一个字符串中搜索匹配正则表达式的第一个位置
re.findall()搜索字符串,以列表类型返回全部能匹配的字符串
re.sub()在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

2.3.2.2.1 re.match()方法

re.match(pattern, string, flags=0)

  • pattern:正则表达式(可以是字符串或原生字符串)
  • string:待匹配字符串
  • flags:正则表达式使用时的控制标记(可选)

常见的控制标记

常用标记说明
re.I re.IGNORECASE忽略正则表达式的大小写,[A-Z]能够匹配小写字符
re.M re.MULTILTNE正则表达式中的^操作符能够将给定字符串的每一行当做匹配开始
re.S re.DOTALL正则表达式中的.操作符能够匹配所有的字符,默认匹配除换行符以外的所有字符

match()方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果,否则,返回None:

import re

# 2.29 re.match()方法的使用
content = 'dfs 234 d 3 34'
result = re.match('..', content)
print(type(result))
print(result)
print(result.group())
print(result.span())

下面是执行结果:

<class 're.Match'>
<re.Match object; span=(0, 2), match='df'>
df
(0, 2)

从结果可以看出,re.match()方法返回一个re.Match对象,该对象有两种常用的方法:

  • group():匹配结果的内容,如果有分组,则分组是从1开始的,group[1]表示第一个分组,group[2]表示第二个分组,以此类推。
  • span():输出匹配到的字符串在原字符串中的起始位置,上面的(0,2)中的0和2就代表了df在’dfs 234 d 3 34’中的起始位置。

正则表达式…匹配到的字符串是df。



2.3.2.2.2 使用分组

上面的例子中,我们用match()方法匹配到了我们想要的字符串,但是,如果我们想要的字符串在匹配结果中该怎么办?

例如:

# 2.30 使用分组()
import re

content = 'a1234567atsea'
result = re.match('a\d{7}a', content)
print(result)
print(result.group())
print(result.span())

我们要在content中将数字提取出来,我们发现数字都包含在a这个字母中间,所以用a\\d{7}a这个正则表达式来提取,\d表示任意一个数字,再用{7}将\d(任意一个数字)匹配7个,运行结果如下:

a1234567a
(0, 9)

我么发现匹配结果中开头和结尾都包含了一个a,如何只得到1234567能,我们只需要使用分组,并将上面的代码修改一下就行了:

import re

# 2.31 分组()的使用
content = 'a1234567atsea'
result = re.match('a(\d{7})a', content)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

a1234567a
(0, 9)
1234567

将正则表达式中的\d{7}的两边加上小括号,将其作为一个分组,在匹配结果中使用group(1)将分组取出,可以看到,我们想要的1234567就在匹配结果中的第一个分组中了(group(1))。



2.3.2.2.3 通用匹配和匹配操作符

在使用正则表达式的时候,要熟练使用.*(通用匹配),其中,.可以匹配任意一个字符,*(星)代表匹配前面的字符无限次,所以它们组合在一起就可以匹配任意字符了(换行符除外)。

下面来看一个通用匹配(.*)的例子:

import re

# 2.32 通用匹配(.*)的使用
content = 'rtgfga1234567atsea'
result = re.match('.*(\d{7}).*', content)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

rtgfga1234567atsea
(0, 18)
1234567

上面的例子中,我们使用了.*来匹配数字前的任意字符串和数字之后的任意字符串,在使用分组获得 我们想要的数字。

有时候,我们要匹配的字符中包括操作符该怎么办?可以使用\来将操作符转义,请看下面的例子:

import re

# 2.33 在正则表达式中使用转义符
content = '12df www.baidu.com df'
result = re.match('12df\s(www\.baidu\.com)\sdf', content)
print(result.group())
print(result.span())
print(result.group(1))

在content中,我们要提取www.baidu.com,其中的.是操作符,在写正则表达式时,我们不能用.来匹配. 因为.代表任意一个字符,我们用\来将其转义,就可以用\.来匹配点了。运行结果如下:

12df www.baidu.com df
(0, 21)
www.baidu.com


2.3.2.2.4 贪婪与非贪婪

由于.*代表任意字符串,这就有一个问题,例如:

import re

# 2.34 贪婪模式
content = 'dfadas1234567assedf'
result = re.match('df.*(\d+).*df', content)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

dfadas1234567assedf
(0, 19)
7

(\d+)分组只匹配到了7这一个数字,我们想要匹配1234567,这是为什么?

这就涉及到了贪婪与非贪婪,由于.*可以匹配任意长度的字符串,所以.*就尽可能的的匹配较多的字符,于是,它就匹配了adas123456,而只让\d+只匹配到一个7。

贪婪:让.*匹配尽可能多的字符

非贪婪:让.*匹配尽可能少的字符

默认.*是贪婪的,要让.*是非贪婪的,只需要在.*的后面加上?,即:.*?

知道如何将.*设置为非贪婪模式后,我们就可以将上面的代码改为如下的代码:

import re

# 2.35 非贪婪模式
content = 'dfadas1234567assedf'
result = re.match('df.*?(\d+).*?df', content)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

dfadas1234567assedf
(0, 19)
1234567

这就匹配到了我们想要的1234567了。



2.3.2.2.5 控制标记

正则表达式可以包含一些可选标志修饰符来控制匹配的模式,常见的控制标记大家可以看上面介绍re.match()方法中列出的控制标记表格。

下面的代码中,我们任然是提取字符串中的所有数字:

import re

content = 'dfadas1234567assedf'
result = re.match('^df.*?(\d+).*?df$', content)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

dfadas1234567assedf
(0, 19)
1234567

我们成功将字符串中的所有数字提取出来了,但是,我将content修改一下:

import re

content = '''dfadas1234567ass
          edf'''
result = re.match('^df.*?(\d+).*?df$', content)
print(result.group())
print(result.span())
print(result.group(1))

再运行:

Traceback (most recent call last):
  File "E:/pycharmWorkStation/venv/Include/draft/test.py", line 6, in <module>
    print(result.group())
AttributeError: 'NoneType' object has no attribute 'group'

Process finished with exit code 1

发现出错了!原因是.匹配任意一个除了换行符之外的任意字符,所以.*?匹配到换行符就不能匹配了,导致我们没有匹配到任何字符,放回结果为None,而在第6行,我们调用了None的group()方法。

要修正这个错误,我们只需要添加一个re.S控制标记,这个标记的作用是使.匹配任意一个包括换行符在内的字符。

import re

# 2.36 控制标记的使用
content = '''dfadas1234567ass
          edf'''
result = re.match('^df.*?(\d+).*?df$', content, re.S)
print(result.group())
print(result.span())
print(result.group(1))

运行结果如下:

dfadas1234567ass
          edf
(0, 30)
1234567

这个re.S控制标记在网页匹配中经常用到。因为HTMl节点经常会换行,加上它,就可以匹配节点与节点中的换行了。



2.3.2.2.6 re.search()

上面讲的match()方法是从字符串的开头开始匹配的,一旦开头就不能匹配,就会导致匹配的失败,请看下面的例子:

import re

# 2.37 re.match()方法是从头开始匹配的
content = 'df colle 123'
result = re.match('colle\s123', content)
print(result.group())
print(result.span())

运行结果出错,没有匹配到结果,返回的是None,None对象没有group()方法。

re.search():在匹配时扫描整个字符串,让后返回成功匹配的第一个结果,如果搜索完了还未找到匹配的,就返回None。也就是说,正则表达式可以是字符串的一部分

import re

# 2.38 re.search()方法的使用
content = 'df colle 123'
result = re.search('colle\s123', content)
print(result.group())
print(result.span())

运行结果:

colle 123
(3, 12)


2.3.2.2.7 re.findall()

上面所讲到的search()方法可以返回匹配到的第一个内容,如果我们想要得到所有匹配到的结果就可以使用findall()方法。

re.findall():搜索字符串,以列表类型返回全部能匹配的子串

import re

# 2.39 re.findall()方法的使用
content = 'df colle 123 colle 123'
result = re.findall('colle\s123', content)
print(result)

运行结果:

['colle 123', 'colle 123']


2.3.2.2.8 re.sub()

re.sub():将文本中所以匹配的内容替换为指定的文本

import re

# 2.40 re.sub()方法的使用
content = 'dfcolle123colle123'
content = re.sub('\d', '', content)
print(content)

运行结果:

dfcollecolle

上面的代码中,我们将content中所有与\d(任意一个数字)匹配的结果替换为’ ',就去掉了content中所有的数字。



2.3.2.2.9 re.compile()

re.compile():将正则字符串编译成正则表达式对象,可以用来复用。

import re

# 2.41 re.compile()方法的使用
content1 = 'dfcolle123colle123'
content2 = 'sdf123e'
content3 = 'ss23dsfd'

pattern = re.compile('\d')

content1 = re.sub(pattern, '', content1)
content2 = re.sub(pattern, '', content2)
content3 = re.sub(pattern, '', content3)
print(content1,' ',content2,' ',content3)

代码中,我们将’\d‘编译为一个正则表达式对象,并在下面的代码中将它复用。

运行结果:

dfcollecolle   sdfe   ssdsfd


2.3.3 使用Re来爬取tx视频

下面来使用re来获取tx视频主页中的所有链接和排行榜中所有的电影名称。

打开tx视频的主页,右击,“检查”,选择“元素”:
在这里插入图片描述
随便找到一个链接,例如我在图中画出来的,根据它写出匹配所有连接的正则表达式:

'"((https|http)://.*?)"'

按照如下的图示来获取请求的URL、user-Agent、cookie
在这里插入图片描述

在这里插入图片描述
重新回到“元素”:

按照如下操作获取排行榜中电影所在的元素:
在这里插入图片描述
重复上面的操作,多获取几个排行榜中电影名所在的元素:

例如:

<span class="rank_title">有翡</span>
<span class="rank_title">我的小确幸</span>
<span class="rank_title">我就是这般女子</span>

根据它写出提取排行榜电影名称的正则表达式为:

‘<span\sclass=“rank_title”>(.*?)’

根据上面步骤得出的信息可以写出如下代码:

import re
import requests

# 2.42 使用re爬取腾讯视频的小例子
url = "https://v.qq.com/"   # 要爬取的链接
headers = {  # 构造请求头
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)'\
                  ' AppleWebKit/537.36 (KHTML, like Gecko) C'\
                  'hrome/88.0.4324.96 Safari/537.36 Edg/88.0.'\
                  '705.53',
    'cookie': 'pgv_pvi=2265568256; pgv_pvid=1230518102; tvfe'\
              '_boss_uuid=19b8c4da4cc7e833; ts_uid=9797476283'\
              '; ts_refer=www.baidu.com/link; RK=VGQx4w/ntj; '\
              'ptcz=fe2dbdf4d8d7efb795bfdc0deaa0429286aa73e0dd'\
              '444a9173fe43fc7b145a1e; ptag=www_baidu_com; ad_pl'\
              'ay_index=77; pgv_info=ssid=s5748488123; ts_last=v.'\
              'qq.com/; bucket_id=9231006; video_guid=8b478b9f33d3db'\
              '89; video_platform=2; qv_als=cyHwW4MZHa8e9NWRA1161181'\
              '1694iJW+zw==',
}
proxy={  # 代理
    "HTTP": "113.3.152.88:8118",
    "HTTPS":"219.234.5.128:3128",
}

r = requests.get(url=url, headers=headers, proxies=proxy, timeout=3)  # 传入url、headers、proxies、timeout构造get请求
print("状态码为:", r.status_code)   # 打印状态码
r.encoding = r.apparent_encoding    # 将编码方式设置为备用编码方式

pattern = re.compile('"((https|http)://.*?)"', re.S)   # 使用re来获取所有的链接,并打印前5个
links = re.findall(pattern, r.text)
print("链接个数为:", len(links))
for i in range(5):
    print(links[i][0])

pattern1 = re.compile('<span\sclass="rank_title">(.*?)</span>', re.S)   # 使用re来获取排行榜中所有的电影名称并
movies = re.findall(pattern1, r.text)

print()   # 将排行榜中的所有电影名称打印出来
for i in movies:
    print(i)

当然,这是我的代码,大家可以将其中的cookie、user-agent和proxy替换为自己的。

运行结果如下:

状态码为: 200
链接个数为: 1331
http://m.v.qq.com/index.html?ptag=
https://puui.qpic.cn/vupload/0/common_logo_square.png/0
http://www.w3.org/2000/svg
https://v.qq.com/
https://film.qq.com/

有翡
我的小确幸
我就是这般女子
暗恋橘生淮南
这个世界不看脸
山海情[原声版]
陀枪师姐2021[普通话版]
黑白禁区
大秦赋
陈情令
长安伏妖
诡婳狐
除暴
赤狐书生
有匪·破雪斩
蜘蛛侠:平行宇宙
重案行动之捣毒任务
武动乾坤:九重符塔
昆仑神宫
绝对忠诚之国家利益
哈哈哈哈哈
王牌对王牌 第6季
欢乐喜剧人 第7季
我就是演员 第3季
平行时空遇见你
现在就告白 第4季
你好生活 第2季
非常完美
乘风破浪的姐姐slay全场
天赐的声音 第2季
斗罗大陆
开心锤锤
狐妖小红娘
雪鹰领主
武神主宰
灵剑尊
猪屁登
万界仙踪

当然,这是我的运行结果,大家的运行结果可能和我不一样,因为排行榜是会变化的。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值