Python自动化开发学习-爬虫1
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python自动化开发学习-爬虫1相关的知识,希望对你有一定的参考价值。
讲师的博客:https://www.cnblogs.com/wupeiqi/articles/6283017.html
建立本地缓存
用下面的命令,就可以把一个页面爬取下来。不过再继续其他操作之前先把爬取的内容在本地建立缓存:
import requests
r = requests.get(‘http://www.autohome.com.cn/news‘) # 爬取页面
print(r.text) # 打印响应的内容
下面会试很多的方法,还是要避免每次都去爬一次相同的页面。主要爬的太频繁,不知道会不会被封。所以爬取过一次之后,在本地建立缓存,之后的各种分析就不用再去爬一遍了。
要缓存的就是 r = requests.get(‘http://www.autohome.com.cn/news‘)
这个,也就是这里的r这个对象。不缓存的话,r是保存在内存中的,程序一旦退出就没有了。这里要做的就是对r这个对象进行序列化,把它保存为本地的文件。由于r是一个python对象,无法使用JSON序列化,这里可以用pickle,保存为一个二进制文件。
序列化与反序列化
首先是把对象序列化,保存为本地的二进制文件:
import pickle
with open(‘test.pk‘, ‘wb‘) as f:
pickle.dump(r, f)
只有再用的时候,就不需要再通过requests.get再去爬一遍了,直接从本地文件中取出内容反序列生成r对象:
import pickle
with open(‘test.pk‘, ‘rb‘) as f:
r = pickle.load(f)
封装个模块
然后,每次自己都要想一下之前有没有缓存过也很麻烦,所以在封装一下,自动判断有没有缓存过。如果没有就去爬网页,然后生成缓存。如果有就去缓存的文件里读。
创建一个文件夹“pk”专门存放缓存的文件。假设测试的python文件是 s1.py 那么就生成一个 pk/s1.pk 的缓存文件,只要判断是否存在该文件,就可以知道是否缓存过了:
import os
import pickle
import requests
def get_pk_name(path):
basedir = os.path.dirname(path)
fullname = os.path.basename(path)
name = os.path.splitext(fullname)[0]
pk_name = ‘%s/pk/%s.%s‘ % (basedir, name, ‘pk‘)
return pk_name
pk_name = get_pk_name(__file__)
response = None
if os.path.exists(pk_name):
print("已经爬取过了,获取缓存的内容...")
with open(pk_name, ‘rb‘) as f:
response = pickle.load(f)
# 只有在没有缓存过页面的时候才进行爬取
if not response:
print("开始爬取页面...")
response = requests.get(‘http://www.autohome.com.cn/news‘)
# 爬完之后记得保存,下次就不用再去爬取了
with open(pk_name, ‘wb‘) as f:
pickle.dump(response, f)
# 从这里开始写真正的代码
print(response.text)
Requests
中文官方文档:http://cn.python-requests.org/zh_CN/latest/user/quickstart.html
安装模块:
pip install requests
发送请求
r = requests.get(‘http://www.autohome.com.cn/news‘)
读取响应内容
print(r.text)
文本编码
上面可能会有乱码,那就是编码不对,可以查看当前的编码,也可以改变它。默认的编码就是 ‘ISO-8859-1‘ :
print(r.encoding)
r.encoding = ‘ISO-8859-1‘
另外还可以自动获取页面的编码,解决乱码问题:
r.encoding = r.apparent_encoding
print(r.text)
二进制响应内容
如果要自己找编码,应该也是在这里面找
print(r.content)
在下载的时候,就要用到二进制的响应内容了
响应状态码
print(r.status_code)
正常返回的状态码是200
Cookie
cookie_obj = r.cookies
cookie_dict = r.cookies.get_dict()
r.cookies 是一个对象,这个对象的的行为和字典类似,也可以像对象那样使用。这里还可以用 get_dict() 方法转成原生的字典。
Beautiful Soup
中文官方文档:https://beautifulsoup.readthedocs.io/
安装模块:
pip install beautifulsoup4
这里继续对上面爬取到的内容进行分析,把爬取到的内容先把编码转正确了,然后这里要分析的是 r.text 文本的响应内容:
import requests
from bs4 import BeautifulSoup
r = requests.get(‘http://www.autohome.com.cn/news‘)
r.encoding = r.apparent_encoding
soup = BeautifulSoup(r.text, features=‘html.parser‘)
features 参数是指定一个处理引擎,这里用的是默认的,效率一般,但是不用额外的安装。如果是生产环境,还有更高效的处理引擎。
这里最后拿到了一个 soup 对象,之后又一系列的方法,可以提取出各种内容。
查找方法
soup.find方法,可以找到第一个符合条件的对象。可以找标签,也可以找id等,还可以多条件组合使用:
soup.find("div")
soup.find(id="link3")
soup.find("div", id="link3")
soup.find_all方法,和find的用法一样,实际上find方法的实现也是调用find_all方法。find_all方法会返回所有符合条件的对象,返回的对象是在一个列表里的。
打印对象和对象的文本
直接打印对象会打印整个html标签,如果只需要标签中的文本,可以通过对象的text属性:
soup = BeautifulSoup(r.text, features=‘html.parser‘)
target = soup.find(‘div‘, {‘class‘: "article-bar"})
print(type(target), target, target.text)
获取对象的所有属性
对象的attrs属性里是这个html标签的所有的属性:
target = soup.find(id=‘auto-channel-lazyload-article‘)
print(target.attrs)
实战
仅凭上面这点知识点就可以开始下面的实战了
爬取汽车之家新网咨询
下面是代码,找到了没一条新闻咨询的a连接的地址,以及标题,最后还把对应的图片下载到了本地(先建一个img文件夹):
# check_cache.py
"""用来检查是否有本地缓存的小模块"""
import os
def get_pk_name(path):
basedir = os.path.dirname(path)
fullname = os.path.basename(path)
name = os.path.splitext(fullname)[0]
pk_name = ‘%s/pk/%s.%s‘ % (basedir, name, ‘pk‘)
return pk_name
# s1.py
"""爬取汽车之家新网咨询"""
import os
import pickle
import requests
from bs4 import BeautifulSoup
from check_cache import get_pk_name
pk_name = get_pk_name(__file__)
response = None
if os.path.exists(pk_name):
print("已经爬取过了,获取缓存的内容...")
with open(pk_name, ‘rb‘) as f:
response = pickle.load(f)
# 只有在没有缓存过页面的时候才进行爬取
if not response:
print("开始爬取页面...")
response = requests.get(‘http://www.autohome.com.cn/news‘)
# 爬完之后记得保存,下次就不用再去爬取了
with open(pk_name, ‘wb‘) as f:
pickle.dump(response, f)
response.encoding = response.apparent_encoding # 获取页面的编码,解决乱码问题
# print(response.text)
soup = BeautifulSoup(response.text, features=‘html.parser‘)
target = soup.find(id=‘auto-channel-lazyload-article‘)
# print(target)
# obj = target.find(‘li‘)
# print(obj)
li_list = target.find_all(‘li‘)
# print(li_list)
for i in li_list:
a = i.find(‘a‘)
# print(a)
# print(a.attrs) # 有些li标签里没有a标签,所以可能会报错
if a: # 这样判断一下就好了
# print(a.attrs) # 这是一个字典
print(a.attrs.get(‘href‘)) # 那就用操作字典的方法来获取值
# tittle = a.find(‘h3‘) # 这个类型是对象
tittle = a.find(‘h3‘).text # 这样拿到的才是文本
print(tittle, type(tittle)) # 不过打印出来差不多,都会变成字符串,差别就是h3这个标签
img_url = a.find(‘img‘).attrs.get(‘src‘)
print(img_url)
# 上面获取到了图片的url,现在可以下载到本地了
img_response = requests.get("http:%s" % img_url)
if ‘/‘ in tittle:
file_name = "img/%s%s" % (tittle.replace(‘/‘, ‘_‘), os.path.splitext(img_url)[1])
else:
file_name = "img/%s%s" % (tittle, os.path.splitext(img_url)[1])
with open(file_name, ‘wb‘) as f:
f.write(img_response.content)
登录抽屉
这里要解决一个登录的问题。
登录有2种,一种是Form表单验证,还有一种是AJAX请求。这是一个使用AJAX做登录请求的网站。
下面是几张浏览器调试工具的截图,主要是要找一下,登录请求需要提交到哪里,提交哪些信息,以及最后会返回的内容。
登录的AJAX请求:
请求正文:
响应正文:
登录请求的代码如下:
import requests
post_dict = {
‘phone‘: ‘8613507293881‘, # 从请求正文里发现,会在手机号前加上86
‘password‘: ‘123456‘,
}
# 所有的请求头可以从请求标头里找到,不过不是必须的
headers = {
‘User-Agent‘: ‘‘, # 这个网站要验证这个请求头,不过只要有就可以通过
}
# 从标头里可以得知,请求的url和请求的方法
response = requests.post(
url=‘https://dig.chouti.com/login‘,
data=post_dict,
headers=headers,
)
print(response.text)
# 这里还有返回的cookies信息,登录成功关键是要拿到成功的cookie
cookie_dict = response.cookies.get_dict()
print(cookie_dict)
登录的套路
上面使用了错误的用户名和密码,在继续登录验证之前,看了解下登录的机制。
登录肯定是要提交验证信息的,一般就用户名和密码。然后请求验证之后,服务端会记录一个session,然后会返回给客户端一个cookie。之后用户每次请求都带着这个cookie,服务端收到请求后就知道这个请求是那个用户提交的了。
不过这个网站有一点不一样,用户在提交验证信息的时候,不但要提交用户名和密码,还要提交一个gpsd。然后服务端验证通过后,会把这次收到的gpsd记录下来。用户之后的cookie里就是要带着这个gpsd就能验证通过。验证请求的gpsd可以从第一次发送get请求的返回的cookie里获取到。另外用户验证通过后,服务端会返回一个cookie,这个cookie里也有一个gpsd,但是是一个新的gpsd,并且是没有用的,这里就会混淆我们,在进行验证这不的时候造成一些困扰。
具体如何应对这类特殊情况,只能用浏览器,打开调试工具,然后一点一点试了。
登录并点赞
下面就是登录验证,获取到第一条咨询的标题和id,发送post请求点赞:
import requests
from bs4 import BeautifulSoup
headers = {
‘User-Agent‘: ‘‘, # 这个网站要验证这个请求头,不过只要有就可以通过
}
r1 = requests.get(‘https://dig.chouti.com‘, headers=headers)
r1_cookies = r1.cookies # 这里有个gpsd,登录验证的时候要一并提交
print(r1_cookies.get_dict())
# 不能把密码上传啊
with open(‘password/s2.txt‘) as f:
auth = f.read()
auth = auth.split(‘
‘)
post_dict = {
‘phone‘: ‘86%s‘ % auth[0], # 从请求正文里发现,会在手机号前加上86
‘password‘: auth[1],
}
# 这个网站的登录机制是,发送验证信息和cookies里的gpsd,成功后给你的gpsd授权
# 之后的请求只有cookies里有这个授权过的gpsd就能认证通过
r2 = requests.post(
url=‘https://dig.chouti.com/login‘,
data=post_dict,
headers=headers,
cookies={‘gpsd‘: r1_cookies[‘gpsd‘]}
)
print(r2.text)
r2_cookies = r2.cookies # 这里也会返回一个新的gpsd,但是无用。
print(r2_cookies.get_dict())
# 获取咨询,然后点赞
r3 = requests.get(
url=‘https://dig.chouti.com‘,
headers=headers,
cookies={‘gpsd‘: r1_cookies[‘gpsd‘]},
)
r3.encoding = r3.apparent_encoding
soup = BeautifulSoup(r3.text, features=‘html.parser‘)
target = soup.find(id=‘content-list‘)
item = target.find(‘div‘, {‘class‘: ‘item‘}) # 就只给第一条点赞吧
news = item.find(‘a‘, {‘class‘: ‘show-content‘}).text
linksId = item.find(‘div‘, {‘class‘: ‘part2‘}).attrs[‘share-linkid‘]
print(‘news:‘, news.strip())
# 点赞
r = requests.post(
url=‘https://dig.chouti.com/link/vote?linksId=%s‘ % linksId,
headers=headers,
cookies={
‘gpsd‘: r1_cookies[‘gpsd‘],
}
)
print(r.text)
Requests 模块详细
找到requests.get()方法的源码,在 requests/api.py 这个文件里,有如下这些方法:
- requests.get()
- requests.options()
- requests.head()
- requests.post()
- requests.put()
- requests.patch()
- requests.delete()
另外还有一个 requests.request() 方法。上面这些方法里最终调用的都是这个request方法。下面就来看下这些方法里都提供了写什么参数。
参数
在 requests.request() 方法里所有的参数如下:
- method : 提交方式。request方法里的参数,其他方法里在调用request方法时,都会填好。
- url : 提交地址
- params : 在url中传递的参数。也就是get方式的参数
- data : 在请求体里传递的参数。
- json : 在请求体里传递的参数。和data不一样,会把json对象序列化后,把整个字符串发出去。
- headers : 请求头。有几个重要的请求头信息,下面会列出
- cookies : 这个就是Cookies。它是放在请求头的Cookie里发送给服务端的。
- files : 上传文件。下面有使用示例
- auth : 设置 HTTP Auth 的认证信息。下面有展开
- timeout : 超时时间。单位是秒,类型是float。也可以是个元祖(connect timeout, read timeout)
- allow_redirects : 是否允许重定向。默认是True。
- proxies : 使用代理。具体怎么用这里没记
- verify : 对于https的请求,如果设为Flase,会忽略证书。
- stream : 下载时的参数,如果是False,则先一次全部下载到内存。如果内容太大,下面有展开。
- cert : 提交请求如果需要附带证书文件,则要设置cert。
data 和 json 参数
这两个参数都是在请求体力传递的参数。但是格式不同,在网络上最终传递的一定都是序列化的字符串。不同的类型会生成一个不同的请求头。在 requests/models.py 文件里可以找到如下的代码:
if not data and json is not None:
content_type = ‘application/json‘
if data:
if isinstance(data, basestring) or hasattr(data, ‘read‘):
content_type = None
else:
content_type = ‘application/x-www-form-urlencoded‘
也就是不同的格式,会设置不同的 Content-Type 请求头:
data 请求头:‘application/x-www-form-urlencoded‘
json 请求头:‘application/json‘
而后端收到请求后,也就可以先查找请求头里的 Content-Type ,然后再解析请求体里的数据。
为什么要用两种格式?
Form表单提交的是data数据,并且Form只能提交字符串或列表,是没有字典的。也就是data这个字典里的value的值只能是字符串或列表,不能是字典。(data字典里不能套字典)
如果就是需要向后端提交一个字典的话,那么只能使用josn了。
请求头
- Referer : 上一次请求的url
- User-Agent : 客户端使用的浏览器
发送文件
这是最基本的用法,字典的key f1,就是Form表单的name。这里实例用了request方法来提交请求,之后的例子只有file_dict不同:
file_dict = {
‘f1‘: open(‘test1.txt‘, rb)
}
requests.request(
method=‘POST‘,
url=‘http://127.0.0.1:8000/test/‘,
files=file_dict
)
定制文件名:
file_dict = {
‘f2‘: (‘mytest.txt‘, open(‘test2.txt‘, rb))
}
定制文件内容(没有文件对象了,文件名当然也得自己定了):
file_dict = {
‘f3‘: (‘test3.txt‘, "自己写内容,或者从文件里读取到的内容")
}
HTTP Auth
HTTP Auth是一种基本连接认证。比如家里用的路由器、ap,用web登录时会弹框(基本登录框,这个不是模态对话框),就是这种认证方式。它会把用户名和密码通过base64加密后放在请求头的 Authorization 里发送出去。
使用的示例代码:
import requests
def param_auth():
from requests.auth import HTTPBasicAuth
ret = requests.get(‘https://api.github.com/user‘, auth=HTTPBasicAuth(‘wupeiqi‘, ‘sdfasdfasdf‘))
print(ret.text)
在 requests.auth 里看到了几个类,应该是不同的加密或者认证方式,但是本质都是把认证信息加密后放在请求头里发送。这里就用 HTTPBasicAuth 举例了。下面是 HTTPBasicAuth 的源码:
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
self.username = username
self.password = password
def __eq__(self, other):
return all([
self.username == getattr(other, ‘username‘, None),
self.password == getattr(other, ‘password‘, None)
])
def __ne__(self, other):
return not self == other
def __call__(self, r):
r.headers[‘Authorization‘] = _basic_auth_str(self.username, self.password)
return r
上面的过程很简单,把用户名和密码通过 _basic_auth_str
方法加密后,加到请求头的 ‘Authorization‘ 里。
这种认证方式比较简单,发布到公网上的网站不会用这种认证方式。
stream 下载
发送完请求,不立即下载全部内容(一次把完整的内容全部下载到内存)。而是通过迭代的方式,一点一点进行下载:
import requests
def param_stream():
from contextlib import closing
with closing(requests.get(‘http://httpbin.org/get‘, stream=True)) as r:
# 在此处理响应。
for i in r.iter_content():
print(i) # 这里用二进制打开个文件写,应该就好了
Session
多次请求的时候,使用 requests.Session() 会自动帮我们管理好Cookie,另外还会设置好一些默认信息,比如请求头等等。
用法如下:
import requests
session = requests.Session() # 生成一个session实例
# 之后的requests请求,使用session替代requests,比如get请求如下
r1 = session.get(‘https://dig.chouti.com‘)
不如看下源码:
class Session(SessionRedirectMixin):
"""A Requests session.
Provides cookie persistence, connection-pooling, and configuration.
Basic Usage::
>>> import requests
>>> s = requests.Session()
>>> s.get(‘http://httpbin.org/get‘)
<Response [200]>
Or as a context manager::
>>> with requests.Session() as s:
>>> s.get(‘http://httpbin.org/get‘)
<Response [200]>
"""
__attrs__ = [
‘headers‘, ‘cookies‘, ‘auth‘, ‘proxies‘, ‘hooks‘, ‘params‘, ‘verify‘,
‘cert‘, ‘prefetch‘, ‘adapters‘, ‘stream‘, ‘trust_env‘,
‘max_redirects‘,
]
除了实例化后使用,还可以像文件操作一样用with的方法使用。
attrs 列表里的值,就是session会自动帮我们设置的所有的属性。
比如headers,它会默认在每次发送的时候添加如下的请求头:
def default_headers():
"""
:rtype: requests.structures.CaseInsensitiveDict
"""
return CaseInsensitiveDict({
‘User-Agent‘: default_user_agent(),
‘Accept-Encoding‘: ‘, ‘.join((‘gzip‘, ‘deflate‘)),
‘Accept‘: ‘*/*‘,
‘Connection‘: ‘keep-alive‘,
})
# User-Agent 的值是这样的,"python-requests/2.19.1" 后面是requests模块的软件版本,会变。
# 可以方便的改掉
s = requests.Session()
s.headers[‘User-Agent‘] = ""
学到这里,之后再发送请求,尤其是要和网站进行多次交互的。就新把Session设置好,然后用Session来请求。所有的设置都会保存在Session的实例里,重复使用,自动管理。
优化登录点赞
之前自动登录点赞的例子,如果使用session改一下就简单多了,完全不用管cookie:
import requests
from bs4 import BeautifulSoup
session = requests.Session()
# 默认的 User-Agent 的值是 "python-requests/2.19.1" 会被反爬,需要改一下
session.headers[‘User-Agent‘] = ""
session.get(‘https://dig.chouti.com‘)
# 不能把密码上传啊
with open(‘password/s2.txt‘) as f:
auth = f.read()
auth = auth.split(‘
‘)
post_dict = {
‘phone‘: ‘86%s‘ % auth[0], # 从请求正文里发现,会在手机号前加上86
‘password‘: auth[1],
}
session.post(‘https://dig.chouti.com/login‘, data=post_dict)
# 获取咨询,然后点赞
r3 = session.get(‘https://dig.chouti.com‘)
r3.encoding = r3.apparent_encoding
soup = BeautifulSoup(r3.text, features=‘html.parser‘)
target = soup.find(id=‘content-list‘)
item = target.find(‘div‘, {‘class‘: ‘item‘})
news = item.find(‘a‘, {‘class‘: ‘show-content‘}).text
linksId = item.find(‘div‘, {‘class‘: ‘part2‘}).attrs[‘share-linkid‘]
print(‘news:‘, news.strip())
# 点赞
r = session.post(‘https://dig.chouti.com/link/vote?linksId=%s‘ % linksId)
print(r.text)
以上是关于Python自动化开发学习-爬虫1的主要内容,如果未能解决你的问题,请参考以下文章