千家信息网

Python自动化开发学习之如何实现爬虫

发表于:2024-09-21 作者:千家信息网编辑
千家信息网最后更新 2024年09月21日,这篇文章主要介绍Python自动化开发学习之如何实现爬虫,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!讲师的博客:https://www.cnblogs.com/wupeiqi
千家信息网最后更新 2024年09月21日Python自动化开发学习之如何实现爬虫

这篇文章主要介绍Python自动化开发学习之如何实现爬虫,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!

讲师的博客:https://www.cnblogs.com/wupeiqi/articles/6283017.html

建立本地缓存

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

import requestsr = 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 picklewith open('test.pk', 'wb') as f:    pickle.dump(r, f)

只有再用的时候,就不需要再通过requests.get再去爬一遍了,直接从本地文件中取出内容反序列生成r对象:

import picklewith open('test.pk', 'rb') as f:    r = pickle.load(f)

封装个模块

然后,每次自己都要想一下之前有没有缓存过也很麻烦,所以在封装一下,自动判断有没有缓存过。如果没有就去爬网页,然后生成缓存。如果有就去缓存的文件里读。
创建一个文件夹"pk"专门存放缓存的文件。假设测试的python文件是 s1.py 那么就生成一个 pk/s1.pk 的缓存文件,只要判断是否存在该文件,就可以知道是否缓存过了:

import osimport pickleimport requestsdef 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_namepk_name = get_pk_name(__file__)response = Noneif 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_encodingprint(r.text)

二进制响应内容
如果要自己找编码,应该也是在这里面找

print(r.content)

在下载的时候,就要用到二进制的响应内容了
响应状态码

print(r.status_code)

正常返回的状态码是200
Cookie

cookie_obj = r.cookiescookie_dict = r.cookies.get_dict()

r.cookies 是一个对象,这个对象的的行为和字典类似,也可以像对象那样使用。这里还可以用 get_dict() 方法转成原生的字典。

Beautiful Soup

中文官方文档:https://beautifulsoup.readthedocs.io/
安装模块:

pip install beautifulsoup4

这里继续对上面爬取到的内容进行分析,把爬取到的内容先把编码转正确了,然后这里要分析的是 r.text 文本的响应内容:

import requestsfrom bs4 import BeautifulSoupr = requests.get('http://www.autohome.com.cn/news')r.encoding = r.apparent_encodingsoup = 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 osdef 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 osimport pickleimport requestsfrom bs4 import BeautifulSoupfrom check_cache import get_pk_namepk_name = get_pk_name(__file__)response = Noneif 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('h4')  # 这个类型是对象        tittle = a.find('h4').text  # 这样拿到的才是文本        print(tittle, type(tittle))  # 不过打印出来差不多,都会变成字符串,差别就是h4这个标签        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 requestspost_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信息,登录成功关键是要拿到成功的cookiecookie_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 requestsfrom bs4 import BeautifulSoupheaders = {    '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_encodingsoup = 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'}).textlinksId = 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 requestsdef 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 requestsproxies1 = {    '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 HTTPProxyAuthauth = HTTPProxyAuth('my_username', 'my_password')  # 这里一次输入用户名和密码r = requests.get('http://www.google.com', proxies=proxies1, auth=auth)

stream 下载
发送完请求,不立即下载全部内容(一次把完整的内容全部下载到内存)。而是通过迭代的方式,一点一点进行下载:

import requestsdef 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 requestssession = 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 requestsfrom bs4 import BeautifulSoupsession = 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_encodingsoup = 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'}).textlinksId = 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自动化开发学习之如何实现爬虫"这篇文章的所有内容,感谢各位的阅读!希望分享的内容对大家有帮助,更多相关知识,欢迎关注行业资讯频道!

0