阿亮亮亮阿

阿亮亮亮阿 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

阿亮亮亮阿 发布了文章 · 1月19日

爬虫系列 | 6、详解爬虫中BeautifulSoup4的用法

bs4,全称BeautifulSoup 4 , 它是Python独有的一种解析方式。也就是说只有Python语言才可以通过这种方式去解析数据。

BeautifulSoup 3 只支持Python2,所以已经被淘汰了。

官网的介绍是这样的

Beautiful Soup 提供一些简单的、python 式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。 Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup 就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。 Beautiful Soup 已成为和 lxml、html6lib 一样出色的 python 解释器,为用户灵活地提供不同的解析策略或强劲的速度。

看起来很复杂,我用自己的理解,通俗的解释一下

我们知道一个网页的源代码,是由多个标签组成,比如<html>、<div>、<td>、<span>等等组成的,而bs4就是用来帮我们精确定位标签位置,从而获取标签或者标签属性中内容的工具。bs4默认自带的解析器,但是官方推荐的是更强大 速度更快的 lxml解析器

其他解析器的优缺点

1561447662879877.png

一、bs4的安装

pip install bs4
pip install lxml

使用bs4解析时,推荐使用lxml解析器。这个在用xpath解析的时候也会用到

二、bs4解析原理

  • 首先实例化一个BeautifulSoup对象,并且将页面源代码加载到这个对象里
  • 调用BeautifulSoup对象中的相关属性或者方法进行标签定位和数据提取
1、如何实例化BeautifuSoup对象
a. 导入bs4包
from bs4 import BeautifulSoup
b.实例化对象

网页源代码,又分为本地已经持久化的HTML文件和网络上直接获取的源代码。

如果是本地已经持久化的文件,可以通过下面的方式将源代码加载到bs4对象中

fp = open('xxx.html', 'r', encoding='utf-8')
# lxml:解析器
soup = BeautifulSoup(fp, 'lxml') 

如果是通过requests库获取的网页源代码,通过下面的方式进行加载

response = requests.get(url)
html = response.text
soup = BeautifulSoup(html, 'lxml')

c.数据解析的方法和属性

bs4能够将复杂的HTML转换成一个树形结构,每个节点都是Python对象。

soup.tagName(标签名): 返回的是文档中第一次出现tagName对应的标签及其相应内容

soup.tageName1.tageName2:返回tag1中tage2的标签及其内容

soup.find:等同于soup.tagName,返回第一个匹配到的对象

soup.find_all:返回所有的匹配到的对象。

通过查看源码会发现,find的本质其实就是调用了find_all, 然后返回第一个元素

参数解释:

  • name :要查找的标签名(字符串、正则、方法、True)
  • attrs: 标签的属性
  • recursive: 递归
  • text: 查找文本
  • **kwargs :其它 键值参数
    def find(self, name=None, attrs={}, recursive=True, text=None,
             **kwargs):
        """Return only the first child of this Tag matching the given
        criteria."""
        r = None
        l = self.find_all(name, attrs, recursive, text, 1, **kwargs)
        if l:
            r = l[0]
        return r

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iXUox6yw-1611066850753)(C:UsersAdministratorAppDataRoamingTyporatypora-user-imagesimage-20210103164834540.png)]

上图是我从某网站截取的部分画面,翻译成HTML如下(只保留了对本次分析有用的部分,为了方便阅读删除了地址的域名信息)

<html>
 <head><titel>测试Title</titel></head>
 <body>
  <div class="test">
      <ul>
          <li> <a href="zhaosi.html">![](123456789.jpg)<p>尼古拉斯赵四</p></a> </li>
      </ul>
  </div>
  <div class="nr_zt w1180">
   <ul>
    <li> <a id="star" href="zhengshuang.html">![](5940f2cd6b759.jpg)<p>郑爽</p></a> </li>
    <li> <a id="star" href="zhuyilong.html">![](5b56e0fabf5bf.jpg)<p>朱一龙</p></a> </li>
    <li> <a id="star" href="zhoudongyu.html">![](5a28b93be8155.jpg)<p>周冬雨</p></a> </li>
    <li> <a id="star" href="huyitian_1.html">![](5aa36dfbe5f61.jpg)<p>胡一天</p></a> </li>
    <li> <a id="star" href="yiyangqianxi.html">![](5a28d243b0382.jpg)<p>易烊千玺</p></a> </li>
    <li> <a id="star" href="dilireba.html">![](5a28b69334087.jpg)<p>迪丽热巴</p></a> </li>
   </ul>
  </div>
 </body>
</html>

看下面几个例子

# 获取第一个li标签
# <li> <a href="http://www.win4000.com/mt/zhengshuang.html">![](http://pic1.win4000.com/tj/2017-06-14/5940f2cd6b759.jpg)<p>郑爽</p></a> </li>
print(soup.li)
# # 获取第一个li标签中a标签
# <a href="http://www.win4000.com/mt/zhengshuang.html">![](http://pic1.win4000.com/tj/2017-06-14/5940f2cd6b759.jpg)<p>郑爽</p></a>
print(soup.li.a)
#获取第一个li标签中a标签
print(soup.find('li').a)
# 获取所有li标签
print(soup.find_all('li'))
# 获取title标签
print(soup.title)
# 获取a标签的父级标签
print(soup.a.parent)
# 获取a标签的父级标签的名字
print(soup.a.parent.name)

如何获取HTML中的href?

分析:href是a标签中的一个属性,而a标签又在li标签中

在bs4中提取标签中的属性可以通过attrs来获取

from bs4 import BeautifulSoup

fp = open('baidu.html', 'r', encoding='utf-8')
soup = BeautifulSoup(fp, 'lxml')

# 如果获取一个可以这样写
result = soup.a.attrs['href']
# zhaosi.html
print(result)

# 获取全部,可通过先获取a标签 然后遍历获取
all_result = soup.find_all('a')
for i in all_result:
    print(i.attrs['href'])

print("* " * 40)
#  如果我只想获取id = star的href,需要先对id进行筛选
# 返回所有包含id=star的a标签
star_result = soup.find_all('a', id='star')
for i in star_result:
    print(i.attrs['href'])

# 返回包含id的标签(只要有id属性,并且有值的标签都返回)
soup.find_all(id=True)

# 假设尼古拉斯赵四 不是第一个a标签中的内容.提取对应的href
# 需要先定位class=‘test’对应div的位置
# 方法一:
result = soup.find('div', 'test')
print(result.a['href'])

# 方法二(class为python中关键字,因此查找html中的class属性需要添加个下划线 class_)
result1 = soup.find('div', class_='test')
print(result1.a['href'])

# 方法三
result2 = soup.find('div', attrs={'class': 'test'})

# 获取第一个a标签中的文本内容
print(soup.a.text)

a_result = soup.find_all('a')
for i in a_result:
    # 生成的是一个迭代器
    print(i.strings)
    print(list(i.strings))
    print(i.string)
    print(i.text)

其他补充

# 返回子孙节点
# children返回迭代器
result = soup.a.children
for i in result:
    print(i)

# 返回子孙节点, contents返回列表
r = soup.a.contents
print(r)

# 可以通过正则对某个属性进行匹配
# 比如返回href中以zh开头的标签
import re
reg = re.compile('^zh')
result = soup.find_all(href=reg)
print(result)

选择器

bs4非常强大,还支持css选择器。通过select来完成

<html>
 <head><titel>测试Title</titel></head>
 <body>
  <div class="test">
      <ul>
          <li> <a href="zhaosi.html">![](123456789.jpg)<p>尼古拉斯赵四</p></a> </li>
      </ul>
  </div>
  <div class="nr_zt w1180">
   <ul>
    <li> <a id="star" href="zhengshuang.html">![](5940f2cd6b759.jpg)<p>郑爽</p></a> </li>
    <li> <a id="star" href="zhuyilong.html">![](5b56e0fabf5bf.jpg)<p>朱一龙</p></a> </li>
    <li> <a id="star" href="zhoudongyu.html">![](5a28b93be8155.jpg)<p>周冬雨</p></a> </li>
    <li> <a id="star" href="huyitian_1.html">![](5aa36dfbe5f61.jpg)<p>胡一天</p></a> </li>
    <li> <a id="star" href="yiyangqianxi.html">![](5a28d243b0382.jpg)<p>易烊千玺</p></a> </li>
    <li> <a id="star" href="dilireba.html">![](5a28b69334087.jpg)<p>迪丽热巴</p></a> </li>
   </ul>
  </div>
 </body>
</html>
from bs4 import BeautifulSoup

fp = open('baidu.html', 'r', encoding='utf-8')
soup = BeautifulSoup(fp, 'lxml')

# 返回一个所有a标签的列表
result = soup.select('a')
# 返回第一个
result1 = soup.select('a')[0]

"""
class选择器 : .className
"""
# 一层一层的进行选择,用 > 连接  即 > : 表示一个层级
# 输出 class = nr_zt 下ul下的li下的a标签集合
a = soup.select('.nr_zt > ul > li > a')
# 多个层级关联,使用 空格。
# 输出 class= 'nr_zt' 下的a标签集合
b = soup.select('.nr_zt a')


"""
id选择器: # idName
"""
result = soup.select('#star')

# 通过href属性查找,返回列表
soup.select('a[href="zhengshuang.html"]')

# 获取对应标签中img标签的src值
a = soup.select('a[href="zhengshuang.html"]')[0]
print(a.img['src']) # 5940f2cd6b759.jpg

以上就是bs4的常用操作代码,实际上在具体的爬虫过程中,匹配的方式比较灵活,所以大家也不用可以的去背,只需要记住其原理即可。

在这里插入图片描述

查看原文

赞 0 收藏 0 评论 0

阿亮亮亮阿 发布了文章 · 1月19日

爬虫系列 | 5、详解爬虫中正则的用法

通过requests库,我们可以轻易的获取到网页的源代码。但是如果想更精细化的提取我们想要的内容,就需要对内容进行解析了。

这个时候我们可以通过一个非常强大的工具来帮助我们 ---- 正则表达式

正则表达式:通过制定一些特殊的字符或者字符组合来过滤字符串,提取或者检索目标的内容。

正则匹配规则如下图所示,来源:CSDN

img

在Python中,re模块拥有全部的正则表达式的功能。下面介绍几个Re中常用的几个方

一、re.match

从字符串的起始位置开始匹配,如果是起始位置匹配成功返回相应字符,如果不是起始位置返回None

语法如下:

def match(pattern, string, flags=0):
  • pattern : 匹配的正则表达式
  • string :被匹配的字符串
  • flags :用于控制正则的匹配方式(如下表)

    修饰符描述
    re.I使匹配对大小写不敏感
    re.L做本地化识别(locale-aware)匹配
    re.M多行匹配,影响 ^ 和 $
    re.S使 . 匹配包括换行在内的所有字符
    re.U根据Unicode字符集解析字符。这个标志影响 w, W, b, B.
    re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解。

匹配之后,会返回一个对象,可以通过group(num) 或者 groups() 获取对应字符串

import re

text = 'Hello World, Hi Python'

# 匹配Hello
m1 = re.match('Hello', text)
# 匹配Hi
m2 = re.match('Hi', text)
if m1:
    print(m1.group())
else:
    print('Hello 字符 匹配失败')

if m2:
    print(m2.group())
else:
    print('Hi 字符 匹配失败')

执行结果:

因为Hello字符串在,text中在起始位置所以匹配成功,而Hi字符不在起始位置,所以返回None

Hello
Hi 字符 匹配失败
在正则表达式中,通过 () 来表示要提取的内容。

在下面的例子中,flags=re.I, 代表着匹配对大小写不敏感(忽略大小写)

text = 'Hello Python'
# . 代表匹配任意除换行以外的字符, * 表示匹配前一个字符(.)0次或多次
m1 = re.match(r'H(.*) p(.*)', text, re.I)
# 匹配整个表达式的字符串
print(m1.group()) # 'Hello Python'
# 返回一个包含所有小组字符串的元组
print(m1.groups()) # ('ello', 'ython')
# 获取第一个元素(从1开始)
print(m1.group(1))  # 'ello'
print(m1.group(2)) # 'ython'

二、re.search()

扫描整个字符并返回第一个匹配成功的

为匹配方便,能用search的时候就不要用match。因为search是匹配整个字符串,直到找到第一个匹配到的字符,而match开头如果匹配不到直接就返回了None。
import re

text = "Nice to meet you Nice to meet all"

s1 = re.search('meet (.*) ', text)
s2 = re.search('meet (.*?) ', text)

print(s1.group())
print(s2.group())

输出结果:

meet you Nice to meet 
meet you 

这里需要注意的是 .*.*?

. 表示 匹配除换行符 n 之外的任何单字符,*表示匹配前面的字符零次或多次。

默认情况下,Python是采用的贪婪模式,就是尽可能多的匹配更多的字符。

所以s1一直匹配到text字符串中最后一个空格。 也就是输出了meet you Nice to meet

如果加上?, 即.*? 则是表示懒惰匹配模式,就是尽可能少的匹配字符。所以s2遇到第一个空格之后就停止了匹配,所以输出meet you

三、re.findall()

在字符串中找到所有与正则匹配的字符串,返回一个列表。如果匹配失败,返回空列表

而match/search 只匹配一次

语法

def findall(pattern, string, flags=0):
import re

text = 'Hello123Python678'
# 匹配字符串中的所有数字
result = re.findall(r'\d+', text)
print(result) # ['123', '678']

四、re.finditer

与findall类似,唯一的区别是返回的是一个迭代器

findall返回的是一个列表

语法

def finditer(pattern, string, flags=0)
import re

text = 'Hello123Python678'
# 匹配字符串中的所有数字
result = re.finditer(r'\d+', text)
print(result) # <callable_iterator object at 0x02BAE700>
for i in result:
    print(i.group())

输出

<callable_iterator object at 0x00B7E640>
123
678

五、re.compile 函数

用于编译正则表达式,生成一个Patter对象,供match()、search()函数使用。

语法

def compile(pattern, flags=0):
  • pattern : 一个字符串形式的正则表达式
  • flags : 可选,表示匹配模式,比如忽略大小写,多行模式等,具体参数为:

    • re.I 忽略大小写
    • re.L 表示特殊字符集 w, W, b, B, s, S 依赖于当前环境
    • re.M 多行模式
    • re.S 即为 . 并且包括换行符在内的任意字符(. 不包括换行符)
    • re.U 表示特殊字符集 w, W, b, B, d, D, s, S 依赖于 Unicode 字符属性数据库
    • re.X 为了增加可读性,忽略空格和 # 后面的注释
pattern.search/match/findall(string, pos: int, endpos: int = ...)
  • string: 等待匹配的字符串
  • pos:可选参数,指定字符串的起始位置,默认为0
  • endpos:可选参数,指定字符串的结束位置,默认为字符串的长度

如下

import re

text = 'Hello123Python678'
pattern = re.compile(r'\d+')

f1 = pattern.findall(text)
# 从下标为6的地方开始匹配
f2 = pattern.findall(text, 6)
# 匹配到下标为8的地方 停止
f3 = pattern.findall(text, endpos=8)
print(f1) # ['123', '678']
print(f2) # ['23', '678']
print(f3) # ['12']

在这里插入图片描述

查看原文

赞 0 收藏 0 评论 0

阿亮亮亮阿 发布了文章 · 1月17日

爬虫系列 | 4、详解Requests的用法

了解了爬虫和网络请求,下面就可以开始正式的了解Python中爬虫相关的模块了

很多爬虫相关的书籍一般刚开始都会讲一下urllib模块,等你跟着书中的学完之后,会告诉你urllib模块用起来比较复杂,通常不使用

确实,urllib是一个比较古老的模块,封装的爬虫方法也相对复杂。所以可以直接开始撸requests模块.

Requests模块的作用就是模拟浏览器发送请求。是Python中原生的一款基于网络请求的模块,不仅功能强大,而且使用起来也比较简单!

模块的安装

直接通过pip进行安装即可

pip install requests

使用起来也非常的简单,分为三步

  • 指定URL,也就是说你要爬取的网站地址
  • 发送请求,请求又分为GET/POST/PUT/DELETE等等
  • 获取响应数据

这样看是不是非常简单呢?废话不多说,来个简单的例子,比如我需要爬取百度的首页

GET请求

# 导入模块
import requests

url = 'https://www.baidu.com'
# 发送get请求
response = requests.get(url)
# 获取网页源代码
baidu_html = response.text
# 将获取的数据进行持久化(保存)
with open('baidu.html', 'w', encoding='utf-8') as f:
    f.write(baidu_html)

执行之后就会在同级目录下有个baidu.html的文件。

以上就是一个最最最基本的使用requests发送get请求的例子。同样如果发送其他请求比如POST/PUT/HEAD等等

requests.post(url)
requests.delete(url)
requests.put(url)
...

在发送请求的时候,大部分时候会携带一些参数的。比如我们进行搜索的时候,发送的GET请求就会携带搜索内容

比如我在百度上搜索python时,url是 https://www.baidu.com/s?wd=python


为了使爬虫程序更加灵活,肯定要将搜索的内容进行分离。可以通过构建一个字典参数,发送GET请求

import requests

# 输入搜索的关键词
wd = input('请输入要搜索的内容:\n')
url = 'https://www.baidu.com/s?'
# 构建get请求的搜索参数
url_param = {'wd': wd}
# 防止爬虫被拦截,增加请求的UA
header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
# 发送GET请求
response = requests.get(url, params=url_param, headers=header)
# 获取网页源代码
wd_html = response.text
# 写入文件,持久化操作
with open('wd.html', 'w', encoding='utf-8') as f:
    f.write(wd_html)

具体的GET用法如下

# url 访问的地址
# params 携带的参数
# **kwargs 其他参数,比如请求头、cookie、代理(proxies)等等
def get(url, params=None, **kwargs):

POST请求

使用httpbin网站进行测试

httpbin是一个能测试HTTP请求和响应的网站,支持各种请求方法

1、以form表单的形式提交参数,只需要将请求参数构造成一个字典,传给data参数即可。

import requests

url = 'http://httpbin.org/post'
params = {'name': '公众号:Python极客专栏', 'language': 'python'}
response = requests.post(url, data=params)
print(response.json())

执行结果:

{
    'args': {},
    'data': '',
    'files': {},
    'form': {
        'language': 'python',
        'name': '公众号:Python极客专栏'
    },
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '99',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Host': 'httpbin.org',
        'User-Agent': 'python-requests/2.22.0',
        'X-Amzn-Trace-Id': 'Root=1-5fef5112-65ad78d4706c58475905fef2'
    },
    'json': None,
    'origin': '',
    'url': 'http://httpbin.org/post'
}

2、以字符串的形式提交参数,通过json.dumps将字典转换成字符串

import requests
import json

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
params = {'name': 'Tom', 'hobby': ['music', 'game']}
# 通过json.dumps 将字典格式化为json字符串
response = requests.post(url, json=json.dumps(params), headers=header)
print(response.json())

执行结果:

{
    'args': {},
    'data': '"{\\"name\\": \\"Tom\\", \\"hobby\\": [\\"music\\", \\"game\\"]}"',
    'files': {},
    'form': {},
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '55',
        'Content-Type': 'application/json',
        'Host': 'httpbin.org',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36', # 请求中加入的headers在这里也可以看到
        'X-Amzn-Trace-Id': 'Root=1-5fef583e-5224201d08e2ff396416e822'
    },
    'json': '{"name": "Tom", "hobby": ["music", "game"]}',
    'origin': '',
    'url': 'http://httpbin.org/post'
}

3、使用post请求提交文件(以multipart形式)

import requests
import json

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
# 读取 baidu.html 文件, 'rb':以二进制形式读取
files = {'file': open('baidu.html', 'rb')}
# post形式传入file文件
response = requests.post(url, files=files)
print(response.json())

执行结果:

{
    'args': {},
    'data': '',
    'files': {
        'file': '<!DOCTYPE html>.....此处省略HTML内容...'
    },
    'form': {},
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '2732',
        'Content-Type': 'multipart/form-data; boundary=1ba2b1406c4a4fe89c1846dc6398dae5',
        'Host': 'httpbin.org',
        'User-Agent': 'python-requests/2.22.0',
        'X-Amzn-Trace-Id': 'Root=1-5fef58f8-68f9fb2246eb190f06092ffb'
    },
    'json': None,
    'origin': '',
    'url': 'http://httpbin.org/post'
}

响应的处理

通过GET/POST等请求会获取到服务器的响应,也就是上面例子中的response。改如何获取更多的信息呢?

import requests

headers = {
    'referer': 'https://www.baidu.com',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
# 读取 baidu.html 文件, 'rb':以二进制形式读取
files = {'file': open('baidu.html', 'rb')}
# post形式传入file文件
response = requests.post(url, files=files, headers=headers, cookies={'test': 'abcdefg'})
# response的处理
# 指定编码
response.encoding = 'utf-8'
print(response.url) # 请求的url
print(response.encoding) # 请求的编码、
print(response.status_code) # 状态码
print(response.content) # 响应内容的二进制形式 (保存文件、图片、音频等)
print(response.text) # 响应内容的文本形式
print(response.json()) # 响应内容的json形式
print(response.headers) # 响应头信息
print(response.request.headers) # 请求头信息
print(response.request.headers['referer']) # 请求头对应属性的内容
print(response.cookies) # cookie信息,返回的cookie对象
print(response.cookies.items())

输出结果如下图

在这里插入图片描述

查看原文

赞 0 收藏 0 评论 0

阿亮亮亮阿 发布了文章 · 1月17日

爬虫系列 | 4、详解Requests的用法

了解了爬虫和网络请求,下面就可以开始正式的了解Python中爬虫相关的模块了

很多爬虫相关的书籍一般刚开始都会讲一下urllib模块,等你跟着书中的学完之后,会告诉你urllib模块用起来比较复杂,通常不使用

确实,urllib是一个比较古老的模块,封装的爬虫方法也相对复杂。所以可以直接开始撸requests模块.

Requests模块的作用就是模拟浏览器发送请求。是Python中原生的一款基于网络请求的模块,不仅功能强大,而且使用起来也比较简单!

模块的安装

直接通过pip进行安装即可

pip install requests

使用起来也非常的简单,分为三步

  • 指定URL,也就是说你要爬取的网站地址
  • 发送请求,请求又分为GET/POST/PUT/DELETE等等
  • 获取响应数据

这样看是不是非常简单呢?废话不多说,来个简单的例子,比如我需要爬取百度的首页

GET请求

# 导入模块
import requests

url = 'https://www.baidu.com'
# 发送get请求
response = requests.get(url)
# 获取网页源代码
baidu_html = response.text
# 将获取的数据进行持久化(保存)
with open('baidu.html', 'w', encoding='utf-8') as f:
    f.write(baidu_html)

执行之后就会在同级目录下有个baidu.html的文件。

以上就是一个最最最基本的使用requests发送get请求的例子。同样如果发送其他请求比如POST/PUT/HEAD等等

requests.post(url)
requests.delete(url)
requests.put(url)
...

在发送请求的时候,大部分时候会携带一些参数的。比如我们进行搜索的时候,发送的GET请求就会携带搜索内容

比如我在百度上搜索python时,url是 https://www.baidu.com/s?wd=python


为了使爬虫程序更加灵活,肯定要将搜索的内容进行分离。可以通过构建一个字典参数,发送GET请求

import requests

# 输入搜索的关键词
wd = input('请输入要搜索的内容:\n')
url = 'https://www.baidu.com/s?'
# 构建get请求的搜索参数
url_param = {'wd': wd}
# 防止爬虫被拦截,增加请求的UA
header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
# 发送GET请求
response = requests.get(url, params=url_param, headers=header)
# 获取网页源代码
wd_html = response.text
# 写入文件,持久化操作
with open('wd.html', 'w', encoding='utf-8') as f:
    f.write(wd_html)

具体的GET用法如下

# url 访问的地址
# params 携带的参数
# **kwargs 其他参数,比如请求头、cookie、代理(proxies)等等
def get(url, params=None, **kwargs):

POST请求

使用httpbin网站进行测试

httpbin是一个能测试HTTP请求和响应的网站,支持各种请求方法

1、以form表单的形式提交参数,只需要将请求参数构造成一个字典,传给data参数即可。

import requests

url = 'http://httpbin.org/post'
params = {'name': '公众号:Python极客专栏', 'language': 'python'}
response = requests.post(url, data=params)
print(response.json())

执行结果:

{
    'args': {},
    'data': '',
    'files': {},
    'form': {
        'language': 'python',
        'name': '公众号:Python极客专栏'
    },
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '99',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Host': 'httpbin.org',
        'User-Agent': 'python-requests/2.22.0',
        'X-Amzn-Trace-Id': 'Root=1-5fef5112-65ad78d4706c58475905fef2'
    },
    'json': None,
    'origin': '',
    'url': 'http://httpbin.org/post'
}

2、以字符串的形式提交参数,通过json.dumps将字典转换成字符串

import requests
import json

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
params = {'name': 'Tom', 'hobby': ['music', 'game']}
# 通过json.dumps 将字典格式化为json字符串
response = requests.post(url, json=json.dumps(params), headers=header)
print(response.json())

执行结果:

{
    'args': {},
    'data': '"{\\"name\\": \\"Tom\\", \\"hobby\\": [\\"music\\", \\"game\\"]}"',
    'files': {},
    'form': {},
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '55',
        'Content-Type': 'application/json',
        'Host': 'httpbin.org',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36', # 请求中加入的headers在这里也可以看到
        'X-Amzn-Trace-Id': 'Root=1-5fef583e-5224201d08e2ff396416e822'
    },
    'json': '{"name": "Tom", "hobby": ["music", "game"]}',
    'origin': '',
    'url': 'http://httpbin.org/post'
}

3、使用post请求提交文件(以multipart形式)

import requests
import json

header = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
# 读取 baidu.html 文件, 'rb':以二进制形式读取
files = {'file': open('baidu.html', 'rb')}
# post形式传入file文件
response = requests.post(url, files=files)
print(response.json())

执行结果:

{
    'args': {},
    'data': '',
    'files': {
        'file': '<!DOCTYPE html>.....此处省略HTML内容...'
    },
    'form': {},
    'headers': {
        'Accept': '*/*',
        'Accept-Encoding': 'gzip, deflate',
        'Content-Length': '2732',
        'Content-Type': 'multipart/form-data; boundary=1ba2b1406c4a4fe89c1846dc6398dae5',
        'Host': 'httpbin.org',
        'User-Agent': 'python-requests/2.22.0',
        'X-Amzn-Trace-Id': 'Root=1-5fef58f8-68f9fb2246eb190f06092ffb'
    },
    'json': None,
    'origin': '',
    'url': 'http://httpbin.org/post'
}

响应的处理

通过GET/POST等请求会获取到服务器的响应,也就是上面例子中的response。改如何获取更多的信息呢?

import requests

headers = {
    'referer': 'https://www.baidu.com',
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36'
}
url = 'http://httpbin.org/post'
# 读取 baidu.html 文件, 'rb':以二进制形式读取
files = {'file': open('baidu.html', 'rb')}
# post形式传入file文件
response = requests.post(url, files=files, headers=headers, cookies={'test': 'abcdefg'})
# response的处理
# 指定编码
response.encoding = 'utf-8'
print(response.url) # 请求的url
print(response.encoding) # 请求的编码、
print(response.status_code) # 状态码
print(response.content) # 响应内容的二进制形式 (保存文件、图片、音频等)
print(response.text) # 响应内容的文本形式
print(response.json()) # 响应内容的json形式
print(response.headers) # 响应头信息
print(response.request.headers) # 请求头信息
print(response.request.headers['referer']) # 请求头对应属性的内容
print(response.cookies) # cookie信息,返回的cookie对象
print(response.cookies.items())

输出结果如下图

查看原文

赞 0 收藏 0 评论 0

阿亮亮亮阿 发布了文章 · 1月16日

爬虫系列 | 3、谷歌浏览器的基本使用

你要问程序员最喜欢的浏览器是什么?

基本都会说当然是谷歌浏览器,它完全遵守了W3C的标准,是最最最受程序员欢迎的浏览器

其次火狐浏览器,安全性高。可以作为备用浏览器。

Edge是微软最新发布的浏览器,和谷歌浏览器内核一样。也是非常不错的

那IE呢? 对不起,打扰了!

在进行爬虫时,不可避免的会用到浏览器的调试功能,需要去查看请求的地址、参数、响应,有时候还需要进行JS调试。

下面呢就给大家介绍一下谷歌浏览器的调试技巧。

谷歌浏览器中打开调试工具的快捷键是F12 或者 Ctrl + Shift + i。 当然也可以按照下图的步骤去打开

按照上图的标注所示

1、用于选择页面中的某个元素来审查或查看相关的信息。 同样在Elements页面下,点击某个Dom元素时,箭头按钮会变成选择状态,页面中也会标识出对应的元素

2、设备按钮,可以进行移动端和PC端的切换。选择不同的移动端设备,并且可以选择不同的尺寸比例。

3、Elements,用于查看修改页面中的元素。包括DOM标签,CSS样式等。修改完之后页面立即生效

比如我通过Elements修改了我的支付宝余额

4、Console,用于打印和输出相关的信息。比如输出错误日志,除此之外还可以执行一些简单的代码

5、Sources,js资源页面,可以在页面中找到浏览器中的js源文件。方便我们进行查看和调试

6、NetWork,网络请求的标签页。可以看到所有的资源请求,并且可以进行请求的筛选

从上图可以看到,请求的状态码、类型、时间、大小等信息

如果点击XHR则只显示异步请求资源,点击具体的请求可以看到请求头信息,参数、请求方法等信息

PreviewResponse中还可以看到具体的响应信息

大部分情况,爬虫的时候只需要通过调试工具去分析请求的方式(POST/GET/..),请求的URL,请求的参数,请求头信息以及响应信息即可。

在这里插入图片描述

查看原文

赞 4 收藏 3 评论 0

阿亮亮亮阿 发布了文章 · 1月13日

爬虫系列 | 2、Http请求和响应,写爬虫这些内容必须掌握

爬虫是建立在网络请求的基础上。所以在开始爬虫之前一定要有一定的网络知识。

一、HTTP协议

HTTP协议,全称为HyperText Transfer Protocol。翻译过来呢就是超文本传输协议,默认端口是80,而HTTPS呢则是在HTTP的基础上加入了SSL层,这样呢就会相对安全,请求不会那么轻易的被别人劫持。默认端口是443。

比如我们经常访问的百度、淘宝、B站、P站 等等 都是HTTPS的

当我们在浏览器中输入网址,按回车后会发生什么?

以下为简述,更多细节可以自行查阅

1、比如我在浏览器中输入www.p_zhan.com (别试了,这是个假网站)。首先会进行DNS解析,获取服务器的IP地址。

2、然后客户端和服务器端会建立TCP链接,链接的过程就是三次握手。通过三次握手之后,客户端与服务端会建立一个可靠的链接,然后就可以进行数据的传输了。

3、发送HTTP请求,浏览器输入的地址请求方式是GET请求!

4、获取服务器的响应。响应码又分为2xx,3xx,4xx,5xx等

5、将内容进行解码,如果数据需要展示,再由浏览器进行渲染。

请求方法:在HTTP/1.1定义的请求虽然有8种,但是掌握以下6种基本就够了

  • GET:发送的请求是获取服务器上的资源。请求体中不会包含请求数据,请求数据会放在协议头上
  • POST:向服务器提交资源,比如表单提交,上传文件等。请求数据会放在请求体重
  • HEAD:本质与GET一样。但是不返回报文主体部分,用于确认URI的有效性以及资源的更新时间等
  • PUT:常用于上传文件。
  • DELETE:删除文件
  • OPTIONS:查询请求的资源支持的请求方法。

响应码:

响应码又称为状态码,表示页面服务器超文本传输协议响应状态的3位数字代码。

1xx: 信息
消息:描述:
100 Continue服务器仅接收到部分请求,但是一旦服务器并没有拒绝该请求,客户端应该继续发送其余的请求。
101 Switching Protocols服务器转换协议:服务器将遵从客户的请求转换到另外一种协议。
103 Checkpoint用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
2xx: 成功
消息:描述:
200 OK请求成功(这是对HTTP请求成功的标准应答。)
201 Created请求被创建完成,同时新的资源被创建。
202 Accepted供处理的请求已被接受,但是处理未完成。
203 Non-Authoritative Information请求已经被成功处理,但是一些应答头可能不正确,因为使用的是其他文档的拷贝。
204 No Content请求已经被成功处理,但是没有返回新文档。浏览器应该继续显示原来的文档。如果用户定期地刷新页面,而Servlet可以确定用户文档足够新,这个状态代码是很有用的。
205 Reset Content请求已经被成功处理,但是没有返回新文档。但浏览器应该重置它所显示的内容。用来强制浏览器清除表单输入内容。
206 Partial Content客户发送了一个带有Range头的GET请求,服务器完成了它。
3xx: 重定向
消息:描述:
300 Multiple Choices多重选择。链接列表。用户可以选择某链接到达目的地。最多允许五个地址。
301 Moved Permanently所请求的页面已经转移至新的 URL 。
302 Found所请求的页面已经临时转移至新的 URL 。
303 See Other所请求的页面可在别的 URL 下被找到。
304 Not Modified未按预期修改文档。客户端有缓冲的文档并发出了一个条件性的请求(一般是提供If-Modified-Since头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓冲的文档还可以继续使用。
305 Use Proxy客户请求的文档应该通过Location头所指明的代理服务器提取。
306 Switch Proxy目前已不再使用,但是代码依然被保留。
307 Temporary Redirect被请求的页面已经临时移至新的 URL 。
308 Resume Incomplete用于 PUT 或者 POST 请求恢复失败时的恢复请求建议。
4xx: 客户端错误
消息:描述:
400 Bad Request因为语法错误,服务器未能理解请求。
401 Unauthorized合法请求,但对被请求页面的访问被禁止。因为被请求的页面需要身份验证,客户端没有提供或者身份验证失败。
402 Payment Required此代码尚无法使用。
403 Forbidden合法请求,但对被请求页面的访问被禁止。
404 Not Found服务器无法找到被请求的页面。
405 Method Not Allowed请求中指定的方法不被允许。
406 Not Acceptable服务器生成的响应无法被客户端所接受。
407 Proxy Authentication Required用户必须首先使用代理服务器进行验证,这样请求才会被处理。
408 Request Timeout请求超出了服务器的等待时间。
409 Conflict由于冲突,请求无法被完成。
410 Gone被请求的页面不可用。
411 Length Required"Content-Length" 未被定义。如果无此内容,服务器不会接受请求。
412 Precondition Failed请求中的前提条件被服务器评估为失败。
413 Request Entity Too Large由于所请求的实体太大,服务器不会接受请求。
414 Request-URI Too Long由于 URL 太长,服务器不会接受请求。当 POST 请求被转换为带有很长的查询信息的 GET 请求时,就会发生这种情况。
415 Unsupported Media Type由于媒介类型不被支持,服务器不会接受请求。
416 Requested Range Not Satisfiable客户端请求部分文档,但是服务器不能提供被请求的部分。
417 Expectation Failed服务器不能满足客户在请求中指定的请求头。
5xx: 服务器错误
消息:描述:
500 Internal Server Error请求未完成。服务器遇到不可预知的情况。
501 Not Implemented请求未完成。服务器不支持所请求的功能,或者服务器无法完成请求。
502 Bad Gateway请求未完成。服务器充当网关或者代理的角色时,从上游服务器收到一个无效的响应。
503 Service Unavailable服务器当前不可用(过载或者当机)。
504 Gateway Timeout网关超时。服务器充当网关或者代理的角色时,未能从上游服务器收到一个及时的响应。
505 HTTP Version Not Supported服务器不支持请求中指明的HTTP协议版本。
511 Network Authentication Required用户需要提供身份验证来获取网络访问入口。

在这里插入图片描述

查看原文

赞 3 收藏 2 评论 0

阿亮亮亮阿 发布了文章 · 1月12日

爬虫系列 | 1、什么是爬虫,玩爬虫的正确姿势有哪些

一、爬虫简介

爬虫是一个帮助我们去寻找网络上小姐姐的照片或视频并且下载到本地的技术

在这里插入图片描述

Emmm... 似乎有点跑题了,百度百科上是这样解释爬虫的

是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。

通俗的来讲,爬虫就是客户端发送请求(浏览器、手机等等),接收服务器的响应,获取目标数据的过程。包括不限于图片、数据、音频、视频等。然后进行持久化的一个过程(数据进行存储)

爬虫的应用在生活中息息相关,比如我们最常用的百度搜索、Google搜索;以及逢年过节用到的抢票软件;网购是用到的比价软件可以查到商品的历史价位;数据网站,比如公众号、抖音、小红书类似的数据分析网站;投票应用,通过一些途径进行刷票刷赞等

二、爬虫是否违法?

看到这是是否后背发凉,老老实实上班写个爬虫,经常加班不涨薪不说,怎么还有牢狱之灾呢?

事实上,因为爬虫被请去喝茶的例子确实不少

  • 北京巧达科技涉及到简历数据业务所有员工被请去喝茶。公司办公室也被查封。

在这里插入图片描述

  • 杨某研究AI人工智能10多年了。通过大量的模型他开发的识别验证码的技术越来越强。再配合撞库,获取了一大批各平台的账号和密码。然后将这些数据通过违法手段进行变现。有兴趣的可以网上搜一下,被称为全国首例利用人工智能犯zui的案件。
  • 上海某科技公司CEO张某,联席CEO&产品负责人宋某,CTO候某,破解了北京某公司的防爬措施后,长期爬取北京数据库的数据。并再次被害单位损失技术服务费2万元。最终以非法获取计算机信息系统数据罪分别判处被告单位罚金20万元,判处被告人张某等四人有期徒刑九个月至一年不等的刑罚及3万元至5万元不等的罚金。

看了上面的案例是不是瑟瑟发抖呢?

在这里插入图片描述

事实上,爬虫在法律中是不被禁止的。但是如果如果利用爬虫获取数据确实是有一定的违法风险。这就好比我们生活中使用的刀,削水果切菜这都没事,但是如果用刀故意伤人这性质就不一样。

我们该如何规避爬虫的风险呢?

1、遵守Robots协议。

Robots协议又称为君子协议,就好比过马路的时候,遇到红灯,我们都知道会停止。但是你如果非要闯红灯,谁也没法阻止你。

同样Robots协议就是标注了哪些内容你不能爬取,但是你非要爬取也没办法。所以被称为君子协议,全凭开发者的自觉性。

Robots通常是放在站点的根目录比如我需要查看淘宝的Robots协议,浏览器输入https://www.taobao.com/robots.txt即可看到

内容如下:

User-agent: Baiduspider
Disallow: /

User-agent: baiduspider
Disallow: /

可以看到淘宝规定了,百度的爬虫禁止(Disallow)在我这里爬取任何内容。

京东的Robots.txt协议 https://www.jd.com/robots.txt

User-agent: * 
Disallow: /?* 
Disallow: /pop/*.html 
Disallow: /pinpai/*.html?* 
User-agent: EtaoSpider 
Disallow: / 
User-agent: HuihuiSpider 
Disallow: / 
User-agent: GwdangSpider 
Disallow: / 
User-agent: WochachaSpider 
Disallow: /

可以看到京东不想让EtaoSpiderHuihuiSpiderGwdangSpiderWochachaSpider这四个爬虫来爬取网站内容。对于其他的爬虫,不希望你爬取/?*,/pop/*.html ,/pinpai/*.html?* 这三个目录的内容

2、控制爬虫的频率

如果对你的爬虫频率毫无限制爬取某个网站,瞬间大量的请求访问服务器,势必增加服务器的压力,一旦服务器瘫痪了,那这种行为就跟网络攻击一个性质了。所以,你品,你细品...

《刑法》第二百八十六条还规定,违反国家规定,对计算机信息系统功能进行删除、修改、增加、干扰,造成计算机信息系统不能正常运行,后果严重的,构成犯罪,处五年以下有期徒刑或者拘役;后果特别严重的,处五年以上有期徒刑。而违反国家规定,对计算机信息系统中存储、处理或者传输的数据和应用程序进行删除、修改、增加的操作,后果严重的,也构成犯罪,依照前款的规定处罚。

3、隐私,敏感信息不要碰

我们平时逛的技术论坛的文章,或者招聘网站的一些公司信息。这些属于公开的内容,是可以放心的爬取。但是如果你通过某些途径,爬取到别人的隐私、敏感信息。这个是属于违法行为,更别提拿这些数据进行获利了。

《网络安全法》第四十四条 任何个人和组织不得窃取或者以其他非法方式获取个人信息。因此,如果爬虫在未经用户同意的情况下大量抓取用户的个人信息,则有可能构成非法收集个人信息的违法行为。

4、违背所有人意愿

知识付费的概念已经出来很久了,如果通过付费账号爬取付费的内容,导致内容提供方丧失了内容的控制权,从而让自己的利益收到了损失。

三、爬虫的矛与盾

爬虫的存在,所以很多网站会有一定的反爬措施。

就好比为了防止外人闯入自己家中每家每户都装了门锁,有的锁可能是PDD几块钱同款的锁,有的锁可能会几十块钱的,而有的则是装了防盗门。

常见的反爬措施有哪些?

  • 请求头(Headers)/User-Agent限制 --- PDD同款

这是最基础的反爬措施,比如浏览器中会从你的请求头中获取Rederer(上级链接),判断这次请求是浏览器请求还是机器请求。

User-Agent是用户访问网站时的浏览器标识。

以上两种方式,我们都可以通过包装请求头或者User-Agent对象进行破解

  • IP限制、访问频率限制

通过判断某个IP或者访问的频率去判断是否是正常行为,可以通过构建代理池去进行爬取,或者每次请求之后都间隔一定的时间来进行爬取

  • 登录或者验证码限制

有些数据必须是登录用户才可以进行爬取,或者在爬取的过程中会是不是的弹出一个验证码校验。魔高一尺道高一丈,可以通过模拟登录或者接入第三方打码平台进行验证码识别

  • Ajax动态加载

有些网站为了避免爬虫,会加载完网页的源代码之后会在浏览器中执行JS程序,然后通过JS代码获取要展现的内容进行异步渲染。

对于这种反爬策略,最简单粗暴的方法就是使用Selenium。

  • JS加密

    在进行请求时,携带了通过js算法生成的加密参数。我没记错的话,网易云音乐就是使用的这种反爬策略。遇到这种情况可以进行js代码调试,然后分析它的逻辑实现,然后让自己的爬虫按照同样的逻辑生成加密参数即可。

  • 互相伤害型

有些网站不得不佩服,它检测到你是一个爬虫程序,不仅不禁止你的访问,还要装作不知道的样子,然后给你返回错误的数据。仿佛在对爬虫说

在这里插入图片描述

在这里插入图片描述

查看原文

赞 1 收藏 1 评论 0

阿亮亮亮阿 发布了文章 · 1月11日

2021年最强的IDEA破解激活办法,放弃你那天天失效的激活码和补丁吧!

一、下载最新的 IDEA 2020 版本安装包


可以选择从 IDEA 官网下载:

https://www.jetbrains.com/idea/download

点击下载,下载完成即可。

二、开始激活


1、 下载完成后,双击 ideaIU-2020.2.exe,打开安装软件;

PS: 确保电脑没有安装老版本软件,如有请卸载。 2020.2只是举例,同样支持最新版本

2、 安装目录选择;

3、 按自己电脑配置勾选:

4、 点击 next, 安心等待其安装完成:

5、 安装完成后,勾选 Run IntelliJ IDEA,点击 finish 运行软件:

6、 会先弹出一个注册框,勾选 Evaluate for free, 点击 Evaluate:

三、激活工作


进入到主界面之后,讲提供的插件移动到IDEA界面中

插件获取:扫描下方二维码,回复 0111

提示重启,

IDEA 重启完成并进入PyCharm主界面,点击菜单【Help】->【Eval Reset】如下图


提示重启

按照下图操作

然后点击Reset,进行PyCharm重启

重启完成后点击菜单【Help】->【About】查看是否继命成功如下图

查看原文

赞 1 收藏 1 评论 2

阿亮亮亮阿 发布了文章 · 1月11日

2021年最新的IntelliJ IDEA安装激活教程,亲测有效!

注意: 本教程适用于 JetBrains 全系列产品 以下所有版本,请放心食用~

一、下载最新的 IDEA 2020 版本安装包


可以选择从 IDEA 官网下载:

https://www.jetbrains.com/idea/download

点击下载,下载完成即可。

二、开始激活


1、 下载完成后,双击 ideaIU-2020.2.exe,打开安装软件;

PS: 确保电脑没有安装老版本软件,如有请卸载。

2、 安装目录选择;

3、 按自己电脑配置勾选:

4、 点击 next, 安心等待其安装完成:

5、 安装完成后,勾选 Run IntelliJ IDEA,点击 finish 运行软件:


IDEA下载安装之后,双击打开运行,会弹出一个如下图所示的对话框。

img

进入到IDEA中,安装提供的插件。如何安装插件:菜单栏中,File --- setting 选择Plugins

img

选择从本地安装插件

img 然后选择,插件路径,点击OK。安装完毕点击弹窗的 Restart 进行重启

img

激活插件查看下方链接:

https://shimo.im/docs/D6hwQdrHdQWdcWXd

重启完毕后,点击Help -> Register在弹出页面中,点击 Add New License

img

img

然后选择 Activation Code 复制提供的补丁Key粘贴进去

image.png

点击Activate,成功激活至2099年
image.png

查看原文

赞 0 收藏 0 评论 0

阿亮亮亮阿 发布了文章 · 2020-11-13

天啦噜!仅仅5张图,彻底搞懂Python中的深浅拷贝

Python中的深浅拷贝

在讲深浅拷贝之前,我们先重温一下 is==的区别。

在判断对象是否相等比较的时候我们可以用is==

  • is:比较两个对象的引用是否相同,即 它们的id 是否一样
  • == : 比较两个对象的值是否相同。
id() ,是Python的一个内置函数,返回对象的唯一标识,用于获取对象的内存地址。

如下

首先,会为整数1分配一个内存空间。 变量a 和 b 都指向了这个内存空间(内存地址相等),所以他们的id相等。

a is bTrue

但是,真的所有整数数字都这样吗? 答案是:不是! 只有在 -25 ~ 256范围中的整数才不会重新分配内存空间。

如下所示:

因为257 超出了范围,所以id不相同,所以a is b返回的值为False。

>>> a = 257
>>> b = 257
>>> print(id(a))
20004752
>>> print(id(b))
20001312
>>> print(a is b)
False
>>> print(a == b)
True

这样做是考虑到性能,Python对-5 到 256 的整数维护了一个数组,相当于一个缓存, 当数值在这个范围内,直接就从数组中返回相对应的引用地址了。如果不在这个范围内,会重新开辟一个新的内存空间。

is 和 == 哪个效率高?

相比之下,is比较的效率更高,因为它只需要判断两个对象的id是否相同即可。

== 则需要重载__eq__ 这个函数,遍历变量中的所有元素内容,逐次比较是否相同。因此效率较低

浅拷贝 深拷贝

给变量进行赋值,有两种方法 直接赋值,拷贝

直接赋值就 = 就可以了。而拷贝又分为浅拷贝和深拷贝

先说结论吧:

  • 浅拷贝:拷贝的是对象的引用,如果原对象改变,相应的拷贝对象也会发生改变
  • 深拷贝:拷贝对象中的每个元素,拷贝对象和原有对象不在有关系,两个是独立的对象

光看上面的概念,对新手来讲可能不太好理解。来看下面的例子吧

赋值

a = [1, 2, 3]
b = a
print(id(a)) # 52531048
print(id(b)) # 52531048

定义变量a,同时将a赋值给b。打印之后发现他们的id是相同的。说明指向了同一个内存地址。

然后修改a的值,再查看他们的id

a = [1, 2, 3]
b = a
print(id(a))  # 46169960
a[1] = 0
print(a, b)  # [1, 0, 3] [1, 0, 3]
print(id(a))  # 46169960
print(id(b))  # 46169960

这时候发现修改后的a和b以及最开始的a的内存地址是一样的。也就是说a和b还是指向了那一块内存,只不过内存里面的[1, 2, 3] 变成了[1, 0, 3]

因为每次重新执行的时候内存地址都是发生改变的,此时的id(a) 的值46169960与52531048是一样的

所以我们就可以判断出,b和a的引用是相同的,当a发生改变的时候,b也会发生改变。

赋值就是:你a无论怎么变,你指向谁,我b就跟着你指向谁。

拷贝

提到拷贝就避免不了可变对象和不可变对象。

  • 可变对象:当有需要改变对象内部的值的时候,这个对象的id不发生变化。
  • 不可变对象:当有需要改变对象内部的值的时候,这个对象的id会发生变化。
a = [1, 2, 3]
print(id(a)) # 56082504
a.append(4)
# 修改列表a之后 id没发生改变,可变对象
print(id(a)) # 56082504

a = 'hello'
print(id(a)) # 59817760
a = a + ' world'
print(id(a)) # 57880072
# 修改字符串a之后,id发生了变化。不可变对象
print(a) # hello world

浅拷贝

拷贝的是不可变对象,一定程度上来讲等同于赋值操作。但是对于多层嵌套结构,浅拷贝只拷贝父对象,不拷贝内部的子对象。

使用copy模块的 copy.copy 进行浅拷贝。

import copy
a = [1, 2, 3]
b = copy.copy(a)
print(id(a))  # 55755880
print(id(b))  # 55737992
a[1] = 0
print(a, b) # [1, 0, 3] [1, 2, 3]

通俗的讲,我将现在的a 复制一份重新分配了一个内存空间。后面你a怎么改变,那跟我b是没有任何关系的。

对于列表的浅拷贝还可以通过list(), list[:] 来实现

但是!我前面提到了对于多层嵌套的结构,需要注意

看下面的例子

import copy
a = [1, 2, [3, 4]]
b = copy.copy(a)

print(id(a)) # 23967528
print(id(b)) # 21738984
# 改变a中的子列表
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4, 5]]  ?? 为什么不是[1, 2, [3, 4]]呢?

b是由a浅拷贝得到的。我修改了a中嵌套的列表,发现b也跟着修改了?

如果还是不太理解,可以参考下图。LIST就是一个嵌套的子对象,指向了另外一个内存空间。所以浅拷贝只是拷贝了元素12 和子对象的引用!

另外一种情况,如果嵌套的是一个元组呢?

import copy
a = [1, 2, (3, 4)]
b = copy.copy(a)

# 改变a中的元组
a[-1] += (5,)
print(a) # [1, 2, (3, 4, 5)]
print(b) # [1, 2, (3, 4)]

我们发现浅拷贝得来的b并没有发生改变。因为元组是不可变对象。改变了元组就会生成新的对象。b中的元组引用还是指向了旧的元组。

深拷贝

所谓深拷贝呢,就是重新分配一个内存空间(新对象),将原对象中的所有元素通过递归的方式进行拷贝到新对象中。

在Python中 通过copy.deepcopy() 来实现深拷贝。

import copy
a = [1, 2, [3, 4]]
b = copy.deepcopy(a)

print(id(a)) # 66587176
print(id(b)) # 66587688
# 改变a中的可变对象
a[-1].append(5)
print(a) # [1, 2, [3, 4, 5]]
print(b) # [1, 2, [3, 4]]  深拷贝之后字列表不会受原来的影响

结语

1、深浅拷贝都会对源对象进行复制,占用不同的内存空间

2、如果源对象没有子目录,则浅拷贝只能拷贝父目录,改动子目录时会影响浅拷贝的对象

3、列表的切片本质就是浅拷贝


史上最全Python资料汇总(长期更新)。隔壁小孩都馋哭了 --- 点击领取

查看原文

赞 3 收藏 2 评论 0

认证与成就

  • 获得 19 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2019-01-25
个人主页被 1.2k 人浏览