python爬虫自动化_Python自动化开发学习-爬虫1

建立本地缓存

用下面的命令,就可以把一个页面爬取下来。不过再继续其他操作之前先把爬取的内容在本地建立缓存:

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

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

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)

获取属性的值

用get方法可以通过属性的key获取到对应的value。下面2个方法都可以:

v1 = target.get('name')

v2 = target.attrs.get('value')

# get方法的源码

def get(self, key, default=None):

"""Returns the value of the 'key' attribute for the tag, or

the value given for 'default' if it doesn't have that

attribute."""

return self.attrs.get(key, default)

实战

仅凭上面这点知识点就可以开始下面的实战了

爬取汽车之家新网咨询

下面是代码,找到了没一条新闻咨询的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('\n')

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 : 在请求体里传递的参数,Form表单提交的内容。

json : 在请求体里传递的参数,AJAX提交的内容。和data不同,会把参数序列化后,把整个字符串发出去。

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' 里。

这种认证方式比较简单,发布到公网上的网站不会用这种认证方式。

proxies 代理

把代理的设置都写在一个字典里,使用代理的设置如下:

import requests

proxies1 = {

'http': '61.172.249.96:80', # http的请求用这个代理

'https': 'http://61.185.219.126:3128', # https的请求用这个代理

}

proxies2 = {'http://10.20.1.128': 'http://10.10.1.10:5323'} # 这特定的站定使用代理

r = requests.get('http://www.google.com', proxies=proxies1)

如果是需要用户名和密码的代理,需要用到上面的auth,这里auth也是一样,是放在请求头里的:

from requests.auth import HTTPProxyAuth

auth = HTTPProxyAuth('my_username', 'my_password') # 这里一次输入用户名和密码

r = requests.get('http://www.google.com', proxies=proxies1, auth=auth)

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')

Or as a context manager::

>>> with requests.Session() as s:

>>> s.get('http://httpbin.org/get')

"""

__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('\n')

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)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值