更新了部分文档

pull/36/head
jackfrued 2021-09-10 07:52:21 +08:00
parent 2834e5b053
commit 09aff5f30c
73 changed files with 801 additions and 575 deletions

View File

@ -1,10 +1,8 @@
## Python语言基础50课
由于之前发布的Python学习项目[Python-100-Days](https://github.com/jackfrued/Python-100-Days)对初学者来说上手还是有一定难度,而且很多小伙伴希望能够有配套的教学视频,最近一段时间也是因为疫情在家办公,所以花了点之间把原来项目中Python语言基础部分单独剥离出来做成了现在这个名为“Python语言基础50课”的项目。现在这个项目**用更为简单通俗的方式重写了原来“Python100天”项目中第1天到第15天的部分****有删减**、**有补充**、**有视频**,力求**对初学者更加友好**,也欢迎大家关注这个持续更新中的项目。国内用户如果访问GitHub比较慢的话也可以关注我的知乎号[Python-Jack](https://www.zhihu.com/people/jackfrued)上的[“从零开始学Python”](<https://zhuanlan.zhihu.com/c_1216656665569013760>)专栏,专栏也在持续更新中,还有大家比较期待的“数据分析”和“面试宝典”的内容也即将上线。**有需要的小伙伴可以关注我在知乎的专栏、文章和回答**,当然,也**欢迎大家评论、收藏和点赞**
由于之前发布的 Python 学习项目 [Python-100-Days](https://github.com/jackfrued/Python-100-Days) 对初学者来说上手还是有一定难度,所以花了点之间把原来项目中 Python 语言基础部分单独剥离出来做成了现在这个名为“Python语言基础50课”的项目。现在这个项目用更为简单通俗的方式重写了原来“Python100天”项目中第1天到第15天的部分**有删减也有补充**,力求**对初学者更加友好**,也欢迎大家关注这个持续更新中的项目。国内用户如果访问 GitHub 比较慢的话,也可以关注我的知乎号 [Python-Jack](https://www.zhihu.com/people/jackfrued) 上的[“从零开始学Python”](<https://zhuanlan.zhihu.com/c_1216656665569013760>)专栏,两边同步更新。有需要的小伙伴可以关注我在知乎的专栏、文章和回答,当然,也欢迎大家评论、收藏和点赞。
近期我们公司组织了大量免费的线上公开课和体验课,对于新手来说,上手一门新语言的时候,如果**有人指导可以少走很多的弯路**。我们的体验课几乎每周都有,我也会**每周更新体验课的信息**,需要参加的小伙伴可以直接**扫描下方的二维码联系我们**,体验课的**班级讨论群**会一直保留,也会有**老师答疑**,希望能够帮助到大家。最为关键的是**这些都是免费的**没有任何人强制你消费一毛钱。在收到体验账号后如果愿意你可以屏蔽掉付费课程销售人员的电话和QQ我们并不会因此将你从学习群中移除掉仍然会一如既往的帮助你。所以希望大家能够理解公司行为和我个人行为的区别我就是一个单纯的程序员我分享这些知识是为了让更多的人学习编程并用它来改变自己的生活。**当键盘侠和五毛党很容易,创作不易,分享自己知识和经验更加不易**。
> **温馨提示**最近国内访问GitHub会因为DNS域名解析服务的问题出现**图片无法显示**的情况,如果你也遇到了这样的问题,可以通过**修改本机的`hosts`文件**直接对GitHub的资源链接进行域名解析来加以解决。使用macOS系统的读者可以参考[《macOS 下三种修改 hosts 文件的方法》](<https://www.jianshu.com/p/752211238c1b>)一文来修改`hosts`文件使用Windows系统的读者可以参考[《在 Windows 上如何管理 hosts 文件》](<https://sspai.com/post/43248>)一文来进行操作。我们可以把下面的内容添加到`hosts`文件的末尾这样就可以解决GitHub上图片无法显示的问题。
最近,国内访问 GitHub 会因为 DNS域名解析服务的问题出现**图片无法显示**的情况,如果你也遇到了这样的问题,可以通过**修改本机的 hosts 文件**直接对 GitHub 的资源链接进行域名解析来加以解决。使用 macOS 系统的读者可以参考[《macOS 下三种修改 hosts 文件的方法》](<https://www.jianshu.com/p/752211238c1b>)一文来修改 hosts 文件;使用 Windows 系统的读者可以参考[《在 Windows 上如何管理 hosts 文件》](<https://sspai.com/post/43248>)一文来进行操作。我们可以把下面的内容添加到 hosts 文件的末尾,这样就可以解决 GitHub 上图片无法显示的问题。
```INI
151.101.184.133 assets-cdn.github.com
@ -14,4 +12,4 @@
151.101.184.133 camo.githubusercontent.com
```
![](res/qrcode-group.png)
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210822092730.png" alt="20933D073D8CCF2D8537AD1E666330E4" width="35%">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 393 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 904 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 717 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 520 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 716 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 198 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

View File

@ -74,4 +74,3 @@ macOS自带了Python 2但是我们需要安装和使用的是Python 3。可
### 总结
Python语言可以做很多的事情也值得我们去学习。要使用Python语言首先需要在自己的计算机上安装Python环境也就是运行Python程序的Python解释器。

View File

@ -31,7 +31,7 @@ else:
如果要构造出更多的分支,可以使用`if...elif...else...`结构或者嵌套的`if...else...`结构,下面的代码演示了如何利用多分支结构实现分段函数求值。
$$
f(x) = \begin{cases} 3x - 5, & x \gt 1 \\ x + 2, & -1 \le x \le 1 \\ 5x + 3, & x \lt -1 \end{cases}
f(x) = \begin{cases} 3x - 5, & (x \gt 1) \\ x + 2, & (-1 \le x \le 1) \\ 5x + 3, & (x \lt -1) \end{cases}
$$
```Python
@ -144,4 +144,3 @@ else:
### 简单的总结
学会了Python中的分支结构和循环结构我们就可以用Python程序来解决很多实际的问题了。这一节课相信已经帮助大家记住了`if`、`elif`、`else`这几个关键字以及如何使用它们来构造分支结构,下一节课我们为大家介绍循环结构,学完这两次课你一定会发现,你能写出很多很多非常有意思的代码。继续加油!

View File

@ -12,7 +12,7 @@ person3 = {'王大锤', 55, 60, '科华北路62号', '中同仁路8号', '131223
说到字典这个词,大家一定不陌生,读小学的时候每个人基本上都有一本《新华字典》,如下图所示。
![](res/dictionary.jpg)
![dictionary](https://gitee.com/jackfrued/mypic/raw/master/20210820204829.jpg)
Python程序中的字典跟现实生活中的字典很像它以键值对键和值的组合的方式把数据组织到一起我们可以通过键找到与之对应的值并进行操作。就像《新华字典》中每个字都有与它对应的解释一样每个字和它的解释合在一起就是字典中的一个条目而字典中通常包含了很多个这样的条目。

View File

@ -64,7 +64,7 @@ def calc(*args, **kwargs):
for value in kwargs.values():
if type(value) in (int, float):
result += value
return total
return result
print(calc()) # 0

View File

@ -227,8 +227,7 @@ print('程序执行结束.')
```Python
try:
with open('guido.jpg', 'rb') as file1, \
open('吉多.jpg', 'wb') as file2:
with open('guido.jpg', 'rb') as file1, open('吉多.jpg', 'wb') as file2:
data = file1.read(512)
while data:
file2.write(data)

View File

@ -1,17 +1,15 @@
## 第22课对象的序列化和反序列化
###读写JSON格式的数据
###JSON概述
通过上面的讲解我们已经知道如何将文本数据和二进制数据保存到文件中那么这里还有一个问题如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢在Python中我们可以将程序中的数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写它本来是JavaScript语言中创建对象的一种字面量语法现在已经被广泛的应用于跨语言跨平台的数据交换。使用JSON的原因非常简单因为它结构紧凑而且是纯文本任何操作系统和编程语言都能处理纯文本这就是**实现跨语言跨平台数据交换**的前提条件。目前JSON基本上已经取代了XML可扩展标记语言作为**异构系统间交换数据的事实标准**。可以在[JSON的官方网站](https://www.json.org/json-zh.html)找到更多关于JSON的知识这个网站还提供了每种语言处理JSON数据格式可以使用的工具或三方库。
下面是JSON格式的一个简单例子大家可能已经注意到了它跟Python中的字典非常类似而且支持嵌套结构就像Python字典中的值可以是字典。如果我们把下面的代码输入到浏览器控制台中它会创建出一个JavaScript中的对象。
```JSON
```JavaScript
{
"name": "骆昊",
"age": 40,
"friends": ["王大锤", "白元芳"],
"cars": [
name: "骆昊",
age: 40,
friends: ["王大锤", "白元芳"],
cars: [
{"brand": "BMW", "max_speed": 240},
{"brand": "Benz", "max_speed": 280},
{"brand": "Audi", "max_speed": 280}
@ -19,29 +17,79 @@
}
```
![](https://gitee.com/jackfrued/mypic/raw/master/20210803201758.png)
上面是JSON的一个简单例子大家可能已经注意到了它跟Python中的字典非常类似而且支持嵌套结构就好比Python字典中的值可以是另一个字典。我们可以尝试把下面的代码输入浏览器的控制台对于Chrome浏览器可以通过“更多工具”菜单找到“开发者工具”子菜单就可以打开浏览器的控制台浏览器的控制台提供了一个运行JavaScript代码的交互式环境类似于Python的交互式环境下面的代码会帮我们创建出一个JavaScript的对象我们将其赋值给名为`obj`的变量。
JSON格式的数据类型和Python中的数据类型也是很容易找到对应关系的正如下面的两张表所示。
```JavaScript
let obj = {
name: "骆昊",
age: 40,
friends: ["王大锤", "白元芳"],
cars: [
{"brand": "BMW", "max_speed": 240},
{"brand": "Benz", "max_speed": 280},
{"brand": "Audi", "max_speed": 280}
]
}
```
| JSON | Python |
| ------------------- | ------------ |
| object | dict |
| array | list |
| string | str |
| number (int / real) | int / float |
| true / false | True / False |
| null | None |
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820143803.png" alt="image-20210820143756353" width="80%">
| Python | JSON |
| -------------------------------------- | ------------ |
| dict | object |
| list, tuple | array |
| str | string |
| int, float, int- & float-derived Enums | number |
| True / False | true / false |
| None | null |
上面的`obj`就是JavaScript中的一个对象我们可以通过`obj.name`或`obj["name"]`两种方式获取到`name`对应的值,如下图所示。可以注意到,`obj["name"]`这种获取数据的方式跟Python字典通过键获取值的索引操作是完全一致的而Python中也通过名为`json`的模块提供了字典与JSON双向转换的支持。
在Python中我们可以使用`json`模块将字典或列表以JSON格式写入到文件中代码如下所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820144411.png" width="85%">
我们在JSON中使用的数据类型JavaScript数据类型和Python中的数据类型也是很容易找到对应关系的大家可以看看下面的两张表。
表1JavaScript数据类型对应的Python数据类型
| JSON | Python |
| ------------ | ------------ |
| `object` |`dict`|
| `array` |`list`|
| `string` | `str` |
| `number ` |`int` / `float`|
| `number` (real) |`float`|
| `boolean` (`true` / `false`) | `bool` (`True` / `False`) |
| `null` | `None` |
表2Python数据类型对应的JavaScript数据类型
| Python | JSON |
| --------------------------- | ---------------------------- |
| `dict` | `object` |
| `list` / `tuple` | `array` |
| `str` | `string` |
| `int` / `float` | `number` |
| `bool` `True` / `False` | `boolean` (`true` / `false`) |
| `None` | `null` |
### 读写JSON格式的数据
在Python中如果要将字典处理成JSON格式以字符串形式存在可以使用`json`模块的`dumps`函数,代码如下所示。
```Python
import json
my_dict = {
'name': '骆昊',
'age': 40,
'friends': ['王大锤', '白元芳'],
'cars': [
{'brand': 'BMW', 'max_speed': 240},
{'brand': 'Audi', 'max_speed': 280},
{'brand': 'Benz', 'max_speed': 280}
]
}
print(json.dumps(my_dict))
```
运行上面的代码输出如下所示可以注意到中文字符都是用Unicode编码显示的。
```JSON
{"name": "\u9a86\u660a", "age": 40, "friends": ["\u738b\u5927\u9524", "\u767d\u5143\u82b3"], "cars": [{"brand": "BMW", "max_speed": 240}, {"brand": "Audi", "max_speed": 280}, {"brand": "Benz", "max_speed": 280}]}
```
如果要将字典处理成JSON格式并写入文本文件只需要将`dumps`函数换成`dump`函数并传入文件对象即可,代码如下所示。
```Python
import json
@ -58,14 +106,9 @@ my_dict = {
}
with open('data.json', 'w') as file:
json.dump(my_dict, file)
print('字典已经保存到data.json文件中')
```
执行上面的代码,会创建`data.json`文件文件的内容如下所示中文是用Unicode编码书写的。
```JSON
{"name": "\u9a86\u660a", "age": 40, "friends": ["\u738b\u5927\u9524", "\u767d\u5143\u82b3"], "cars": [{"brand": "BMW", "max_speed": 240}, {"brand": "Audi", "max_speed": 280}, {"brand": "Benz", "max_speed": 280}]}
```
执行上面的代码,会创建`data.json`文件,文件的内容跟上面代码的输出是一样的。
`json`模块有四个比较重要的函数,分别是:
@ -76,7 +119,7 @@ print('字典已经保存到data.json文件中')
这里出现了两个概念,一个叫序列化,一个叫反序列化,[维基百科](https://zh.wikipedia.org/)上的解释是“序列化serialization在计算机科学的数据处理中是指将数据结构或对象状态转换为可以存储或传输的形式这样在需要的时候能够恢复到原先的状态而且通过序列化的数据重新获取字节时可以利用这些字节来产生原始对象的副本拷贝。与这个过程相反的动作即从一系列字节中提取数据结构的操作就是反序列化deserialization”。
我们可以通过下面的代码,从上面创建的`data.json`文件中读取JSON格式的数据并还原成字典。
我们可以通过下面的代码,读取上面创建的`data.json`文件将JSON格式的数据还原成Python中的字典。
```Python
import json
@ -100,7 +143,7 @@ pip install ujson
在默认情况下pip会访问`https://pypi.org/simple/`来获得三方库相关的数据,但是国内访问这个网站的速度并不是十分理想,因此国内用户可以使用豆瓣网提供的镜像来替代这个默认的下载源,操作如下所示。
```Bash
pip install ujson -i https://pypi.doubanio.com/simple
pip install ujson
```
可以通过`pip search`命令根据名字查找需要的三方库,可以通过`pip list`命令来查看已经安装过的三方库。如果想更新某个三方库,可以使用`pip install -U`或`pip install --upgrade`;如果要删除某个三方库,可以使用`pip uninstall`命令。
@ -109,6 +152,7 @@ pip install ujson -i https://pypi.doubanio.com/simple
```Bash
pip search ujson
micropython-cpython-ujson (0.2) - MicroPython module ujson ported to CPython
pycopy-cpython-ujson (0.2) - Pycopy module ujson ported to CPython
ujson (3.0.0) - Ultra fast JSON encoder and decoder for Python
@ -131,6 +175,7 @@ decimal-monkeypatch (0.4.3) - Python 2 performance patches: decimal to cdec
```Bash
pip list
Package Version
----------------------------- ----------
aiohttp 3.5.4
@ -143,21 +188,7 @@ amqp 2.4.2
更新`ujson`三方库。
```Bash
pip install -U ujson -i https://pypi.doubanio.com/simple
```
如果要更新pip本身可以使用下面的命令。
macOS系统
```Bash
pip3 install -U pip
```
Windows系统
```Bash
python -m pip install -U pip
pip install -U ujson
```
删除`ujson`三方库。
@ -166,16 +197,20 @@ python -m pip install -U pip
pip uninstall -y ujson
```
> **提示**:如果要更新`pip`自身对于macOS系统来说可以使用命令`pip install -U pip`。在Windows系统上可以将命令替换为`python -m pip install -U --user pip`。
### 使用网络API获取数据
如果想在我们自己的程序中显示天气、路况、航班等信息这些信息我们自己没有能力提供所以必须使用网络数据服务。目前绝大多数的网络数据服务或称之为网络API都是基于[HTTP](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE)提供JSON格式的数据在Python程序中我们可以发送HTTP请求给指定的URL统一资源定位符这个URL就是所谓的网络API如果请求成功它会返回HTTP响应而HTTP响应的消息体中就有我们需要的JSON格式的数据。关于HTTP的相关知识可以看看阮一峰的[《HTTP协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)一文。
如果想在我们自己的程序中显示天气、路况、航班等信息这些信息我们自己没有能力提供所以必须使用网络数据服务。目前绝大多数的网络数据服务或称之为网络API都是基于[HTTP](https://zh.wikipedia.org/wiki/%E8%B6%85%E6%96%87%E6%9C%AC%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE)或HTTPS提供JSON格式的数据我们可以通过Python程序发送HTTP请求给指定的URL统一资源定位符这个URL就是所谓的网络API如果请求成功它会返回HTTP响应而HTTP响应的消息体中就有我们需要的JSON格式的数据。关于HTTP的相关知识可以看看阮一峰的[《HTTP协议入门》](http://www.ruanyifeng.com/blog/2016/08/http.html)一文。
国内有很多提供网络API接口的网站例如[聚合数据](https://www.juhe.cn/)、[阿凡达数据](http://www.avatardata.cn/)等,这些网站上有免费的和付费的数据接口,国外的[{API}Search](http://apis.io/)网站也提供了类似的功能,有兴趣的可以自行研究。下面的例子演示了如何使用[`requests`](http://docs.python-requests.org/zh_CN/latest/)库基于HTTP进行网络资源访问的三方库访问网络API获取国内新闻并显示新闻标题和链接,这个例子使用了[天行数据](https://www.tianapi.com/)提供的国内新闻数据接口其中的APIKey需要自己到网站上注册申请。
国内有很多提供网络API接口的网站例如[聚合数据](https://www.juhe.cn/)、[阿凡达数据](http://www.avatardata.cn/)等,这些网站上有免费的和付费的数据接口,国外的[{API}Search](http://apis.io/)网站也提供了类似的功能,有兴趣的可以自行研究。下面的例子演示了如何使用[`requests`](http://docs.python-requests.org/zh_CN/latest/)库基于HTTP进行网络资源访问的三方库访问网络API获取国内新闻并显示新闻标题和链接。在这个例子中,我们使用了名为[天行数据](https://www.tianapi.com/)的网站提供的国内新闻数据接口其中的APIKey需要自己到网站上注册申请。在天行数据网站注册账号后会自动分配APIKey但是要访问接口获取数据需要绑定验证邮箱或手机然后还要申请需要使用的接口如下图所示。
安装`requests`库。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820151134.png" alt="image-20210820151134034" width="100%">
Python通过URL接入网络我们推荐大家使用`requests`三方库,它简单且强大,但需要自行安装。
```Bash
pip install requests -i https://pypi.doubanio.com/simple/
pip install requests
```
获取国内新闻并显示新闻标题和链接。
@ -192,7 +227,11 @@ if resp.status_code == 200:
print('-' * 60)
```
> **注意**上面代码中的APIKey需要换成自己在天行数据网站申请的APIKey同时还要申请开通国内新闻的接口才能获取到JSON格式的数据。这个网站上还有很多非常有意思的网络API接口例如垃圾分类、舔狗日记、周公解梦等等大家可以仿照上面的代码来调用这些接口。
上面的代码通过`requests`模块的`get`函数向天行数据的国内新闻接口发起了一次请求,如果请求过程没有出现问题,`get`函数会返回一个`Response`对象,通过该对象的`status_code`属性表示HTTP响应状态码如果不理解没关系你只需要关注它的值如果值等于`200`或者其他`2`字头的值,那么我们的请求是成功的。通过`Response`对象的`json()`方法可以将返回的JSON格式的数据直接处理成Python字典非常方便。天行数据国内新闻接口返回的JSON格式的数据部分如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820154455.png" width="100%">
> **提示**上面代码中的APIKey需要换成自己在天行数据网站申请的APIKey。天行数据网站上还有提供了很多非常有意思的API接口例如垃圾分类、周公解梦等大家可以仿照上面的代码来调用这些接口。每个接口都有对应的接口文档文档中有关于如何使用接口的详细说明。
### 简单的总结

View File

@ -57,6 +57,8 @@ print(sheet.row_values(0))
print(sheet.row_slice(3, 0, 5))
```
> **提示**上面代码中使用的Excel文件“阿里巴巴2020年股票数据.xls”可以通过后面的百度云盘地址进行获取。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g 提取码:e7b4。
相信通过上面的代码大家已经了解到了如何读取一个Excel文件如果想知道更多关于`xlrd`模块的知识,可以阅读它的[官方文档](https://xlrd.readthedocs.io/en/latest/)。
### 写Excel文件
@ -183,7 +185,7 @@ sheet2.write(nrows, 6, xlwt.Formula(f'sum(G2:G{nrows})'))
wb_for_write.save('阿里巴巴2020年股票数据汇总.xls')
```
> **说明**:上面的代码有一些小瑕疵,有兴趣的读者可以自行探索如何解决。
> **说明**:上面的代码有一些小瑕疵,有兴趣的读者可以自行探索并思考如何解决。
### 简单的总结

View File

@ -55,6 +55,8 @@ for row_ch in range(2, sheet.max_row + 1):
print()
```
> **提示**上面代码中使用的Excel文件“阿里巴巴2020年股票数据.xlsx”可以通过后面的百度云盘地址进行获取。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g 提取码:e7b4。
需要提醒大家一点,`openpyxl`获取指定的单元格有两种方式,一种是通过`cell`方法,需要注意,该方法的行索引和列索引都是从`1`开始的这是为了照顾用惯了Excel的人的习惯另一种是通过索引运算通过指定单元格的坐标例如`C3`、`G255`,也可以取得对应的单元格,再通过单元格对象的`value`属性,就可以获取到单元格的值。通过上面的代码,相信大家还注意到了,可以通过类似`sheet['A2:C5']`或`sheet['A2':'C5']`这样的切片操作获取多个单元格,该操作将返回嵌套的元组,相当于获取到了多行多列。
### 写Excel文件

View File

@ -83,6 +83,8 @@ document.add_page_break()
document.save('demo.docx')
```
> **提示**上面代码第7行中的注释`# type: Doc`是为了在PyCharm中获得代码补全提示因为如果不清楚对象具体的数据类型PyCharm无法在后续代码中给出`Doc`对象的代码补全提示。
执行上面的代码打开生成的Word文档效果如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820002742.png" alt="image-20210820002742341" width="40%">&nbsp;&nbsp;<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820002843.png" alt="image-20210820002843696" width="40%">
@ -98,7 +100,7 @@ for no, p in enumerate(doc.paragraphs):
print(no, p.text)
```
> **提示**如果需要上面代码中的Word文件可以通过下面的百度云盘地址进行获取。
> **提示**如果需要上面代码中的Word文件可以通过下面的百度云盘地址进行获取。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g 提取码:e7b4。
读取到的内容如下所示。
@ -238,3 +240,6 @@ pres.save('test.pptx')
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820010306.png" alt="image-20210820010306008" width="75%" />
### 简单的总结
用Python程序解决办公自动化的问题真的非常酷它可以将我们从繁琐乏味的劳动中解放出来。写这类代码就是去做一件一劳永逸的事情写代码的过程即便不怎么愉快使用这些代码的时候应该是非常开心的。

View File

@ -0,0 +1,158 @@
## 第27课用Python操作PDF文件
PDF是Portable Document Format的缩写这类文件通常使用`.pdf`作为其扩展名。在日常开发工作中最容易遇到的就是从PDF中读取文本内容以及用已有的内容生成PDF文档这两个任务。
### 从PDF中提取文本
在Python中可以使用名为`PyPDF2`的三方库来读取PDF文件可以使用下面的命令来安装它。
```Bash
pip install PyPDF2
```
`PyPDF2`没有办法从PDF文档中提取图像、图表或其他媒体但它可以提取文本并将其返回为Python字符串。
```Python
import PyPDF2
reader = PyPDF2.PdfFileReader('test.pdf')
page = reader.getPage(0)
print(page.extractText())
```
> **提示**上面代码中使用的PDF文件“test.pdf”以及下面的代码中需要用到的PDF文件都可以通过后面的百度云盘地址进行获取。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g 提取码:e7b4。
当然,`PyPDF2`并不是什么样的PDF文档都能提取出文字来这个问题就我所知并没有什么特别好的解决方法尤其是在提取中文的时候。网上也有很多讲解从PDF中提取文字的文章推荐大家自行阅读[《三大神器助力Python提取pdf文档信息》](https://cloud.tencent.com/developer/article/1395339)一文进行了解。
要从PDF文件中提取文本也可以直接使用三方的命令行工具具体的做法如下所示。
```Bash
pip install pdfminer.six
pdf2text.py test.pdf
```
### 旋转和叠加页面
上面的代码中通过创建`PdfFileReader`对象的方式来读取PDF文档该对象的`getPage`方法可以获得PDF文档的指定页并得到一个`PageObject`对象,通过`PageObject`对象的`rotateClockwise`和`rotateCounterClockwise`方法可以实现页面的顺时针和逆时针方向旋转,通过`PageObject`对象的`addBlankPage`方法可以添加一个新的空白页,代码如下所示。
```Python
import PyPDF2
from PyPDF2.pdf import PageObject
# 创建一个读PDF文件的Reader对象
reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
# 创建一个写PDF文件的Writer对象
writer = PyPDF2.PdfFileWriter()
# 对PDF文件所有页进行循环遍历
for page_num in range(reader.numPages):
# 获取指定页码的Page对象
current_page = reader.getPage(page_num) # type: PageObject
if page_num % 2 == 0:
# 奇数页顺时针旋转90度
current_page.rotateClockwise(90)
else:
# 偶数页反时针旋转90度
current_page.rotateCounterClockwise(90)
writer.addPage(current_page)
# 最后添加一个空白页并旋转90度
page = writer.addBlankPage() # type: PageObject
page.rotateClockwise(90)
# 通过Writer对象的write方法将PDF写入文件
with open('resources/XGBoost-modified.pdf', 'wb') as file:
writer.write(file)
```
### 加密PDF文件
使用`PyPDF2`中的`PdfFileWrite`对象可以为PDF文档加密如果需要给一系列的PDF文档设置统一的访问口令使用Python程序来处理就会非常的方便。
```Python
import PyPDF2
reader = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
writer = PyPDF2.PdfFileWriter()
for page_num in range(reader.numPages):
writer.addPage(reader.getPage(page_num))
# 通过encrypt方法加密PDF文件方法的参数就是设置的密码
writer.encrypt('foobared')
with open('resources/XGBoost-encrypted.pdf', 'wb') as file:
writer.write(file)
```
### 批量添加水印
上面提到的`PageObject`对象还有一个名为`mergePage`的方法可以两个PDF页面进行叠加通过这个操作我们很容易实现给PDF文件添加水印的功能。例如要给上面的“XGBoost.pdf”文件添加一个水印我们可以先准备好一个提供水印页面的PDF文件然后将包含水印的`PageObject`读取出来然后再循环遍历“XGBoost.pdf”文件的每个页获取到`PageObject`对象,然后通过`mergePage`方法实现水印页和原始页的合并,代码如下所示。
```Python
import PyPDF2
from PyPDF2.pdf import PageObject
reader1 = PyPDF2.PdfFileReader('resources/XGBoost.pdf')
reader2 = PyPDF2.PdfFileReader('resources/watermark.pdf')
writer = PyPDF2.PdfFileWriter()
# 获取水印页
watermark_page = reader2.getPage(0)
for page_num in range(reader1.numPages):
current_page = reader1.getPage(page_num) # type: PageObject
current_page.mergePage(watermark_page)
# 将原始页和水印页进行合并
writer.addPage(current_page)
# 将PDF写入文件
with open('resources/XGBoost-watermarked.pdf', 'wb') as file:
writer.write(file)
```
如果愿意,还可以让奇数页和偶数页使用不同的水印,大家可以自己思考下应该怎么做。
### 创建PDF文件
创建PDF文档需要三方库`reportlab`的支持,安装的方法如下所示。
```Bash
pip install reportlab
```
下面通过一个例子为大家展示`reportlab`的用法。
```Python
from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
pdf_canvas = canvas.Canvas('resources/demo.pdf', pagesize=A4)
width, height = A4
# 绘图
image = canvas.ImageReader('resources/guido.jpg')
pdf_canvas.drawImage(image, 20, height - 395, 250, 375)
# 显示当前页
pdf_canvas.showPage()
# 注册字体文件
pdfmetrics.registerFont(TTFont('Font1', 'resources/fonts/Vera.ttf'))
pdfmetrics.registerFont(TTFont('Font2', 'resources/fonts/青呱石头体.ttf'))
# 写字
pdf_canvas.setFont('Font2', 40)
pdf_canvas.setFillColorRGB(0.9, 0.5, 0.3, 1)
pdf_canvas.drawString(width // 2 - 120, height // 2, '你好,世界!')
pdf_canvas.setFont('Font1', 40)
pdf_canvas.setFillColorRGB(0, 1, 0, 0.5)
pdf_canvas.rotate(18)
pdf_canvas.drawString(250, 250, 'hello, world!')
# 保存
pdf_canvas.save()
```
上面的代码如果不太理解也没有关系等真正需要用Python创建PDF文档的时候再好好研读一下`reportlab`的[官方文档](https://www.reportlab.com/docs/reportlab-userguide.pdf)就可以了。
> **提示**:上面代码中用到的图片和字体,可以在后面的百度云盘链接中获取。链接:https://pan.baidu.com/s/1rQujl5RQn9R7PadB2Z5g_g 提取码:e7b4。
### 简单的总结
在学习完上面的内容之后相信大家已经知道像合并多个PDF文件这样的工作应该如何用Python代码来处理了赶紧自己动手试一试吧。

View File

@ -0,0 +1,171 @@
## 第28课用Python处理图像
### 入门知识
1. 颜色。如果你有使用颜料画画的经历那么一定知道混合红、黄、蓝三种颜料可以得到其他的颜色事实上这三种颜色就是美术中的三原色它们是不能再分解的基本颜色。在计算机中我们可以将红、绿、蓝三种色光以不同的比例叠加来组合成其他的颜色因此这三种颜色就是色光三原色。在计算机系统中我们通常会将一个颜色表示为一个RGB值或RGBA值其中的A表示Alpha通道它决定了透过这个图像的像素也就是透明度
| 名称 | RGB值 | 名称 | RGB值 |
| :---------: | :-------------: | :----------: | :-----------: |
| White | (255, 255, 255) | Red | (255, 0, 0) |
| Green绿 | (0, 255, 0) | Blue | (0, 0, 255) |
| Gray | (128, 128, 128) | Yellow | (255, 255, 0) |
| Black | (0, 0, 0) | Purple | (128, 0, 128) |
2. 像素。对于一个由数字序列表示的图像来说最小的单位就是图像上单一颜色的小方格这些小方块都有一个明确的位置和被分配的色彩数值而这些一小方格的颜色和位置决定了该图像最终呈现出来的样子它们是不可分割的单位我们通常称之为像素pixel。每一个图像都包含了一定量的像素这些像素决定图像在屏幕上所呈现的大小大家如果爱好拍照或者自拍对像素这个词就不会陌生。
### 用Pillow处理图像
Pillow是由从著名的Python图像处理库PIL发展出来的一个分支通过Pillow可以实现图像压缩和图像处理等各种操作。可以使用下面的命令来安装Pillow。
```Shell
pip install pillow
```
Pillow中最为重要的是`Image`类,可以通过`Image`模块的`open`函数来读取图像并获得`Image`类型的对象。
1. 读取和显示图像
```Python
from PIL import Image
# 读取图像获得Image对象
image = Image.open('guido.jpg')
# 通过Image对象的format属性获得图像的格式
print(image.format) # JPEG
# 通过Image对象的size属性获得图像的尺寸
print(image.size) # (500, 750)
# 通过Image对象的mode属性获取图像的模式
print(image.mode) # RGB
# 通过Image对象的show方法显示图像
image.show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202628.png" width="80%">
2. 剪裁图像
```Python
# 通过Image对象的crop方法指定剪裁区域剪裁图像
image.crop((80, 20, 310, 360)).show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202701.png" width="80%">
3. 生成缩略图
```Python
# 通过Image对象的thumbnail方法生成指定尺寸的缩略图
image.thumbnail((128, 128))
image.show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202722.png" width="100%">
4. 缩放和黏贴图像
```Python
# 读取骆昊的照片获得Image对象
luohao_image = Image.open('luohao.png')
# 读取吉多的照片获得Image对象
guido_image = Image.open('guido.jpg')
# 从吉多的照片上剪裁出吉多的头
guido_head = guido_image.crop((80, 20, 310, 360))
width, height = guido_head.size
# 使用Image对象的resize方法修改图像的尺寸
# 使用Image对象的paste方法将吉多的头粘贴到骆昊的照片上
luohao_image.paste(guido_head.resize((int(width / 1.5), int(height / 1.5))), (172, 40))
luohao_image.show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202749.png" width="80%">
5. 旋转和翻转
```Python
image = Image.open('guido.jpg')
# 使用Image对象的rotate方法实现图像的旋转
image.rotate(45).show()
# 使用Image对象的transpose方法实现图像翻转
# Image.FLIP_LEFT_RIGHT - 水平翻转
# Image.FLIP_TOP_BOTTOM - 垂直翻转
image.transpose(Image.FLIP_TOP_BOTTOM).show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202829.png" width="80%">
6. 操作像素
```Python
for x in range(80, 310):
for y in range(20, 360):
# 通过Image对象的putpixel方法修改图像指定像素点
image.putpixel((x, y), (128, 128, 128))
image.show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202932.png" width="80%">
7. 滤镜效果
```Python
from PIL import ImageFilter
# 使用Image对象的filter方法对图像进行滤镜处理
# ImageFilter模块包含了诸多预设的滤镜也可以自定义滤镜
image.filter(ImageFilter.CONTOUR).show()
```
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803202953.png" width="80%">
### 使用Pillow绘图
Pillow中有一个名为`ImageDraw`的模块,该模块的`Draw`函数会返回一个`ImageDraw`对象,通过`ImageDraw`对象的`arc`、`line`、`rectangle`、`ellipse`、`polygon`等方法,可以在图像上绘制出圆弧、线条、矩形、椭圆、多边形等形状,也可以通过该对象的`text`方法在图像上添加文字。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803203016.png" width="80%">
要绘制如上图所示的图像,完整的代码如下所示。
```Python
import random
from PIL import Image, ImageDraw, ImageFont
def random_color():
"""生成随机颜色"""
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
return red, green, blue
width, height = 800, 600
# 创建一个800*600的图像背景色为白色
image = Image.new(mode='RGB', size=(width, height), color=(255, 255, 255))
# 创建一个ImageDraw对象
drawer = ImageDraw.Draw(image)
# 通过指定字体和大小获得ImageFont对象
font = ImageFont.truetype('Kongxin.ttf', 32)
# 通过ImageDraw对象的text方法绘制文字
drawer.text((300, 50), 'Hello, world!', fill=(255, 0, 0), font=font)
# 通过ImageDraw对象的line方法绘制两条对角直线
drawer.line((0, 0, width, height), fill=(0, 0, 255), width=2)
drawer.line((width, 0, 0, height), fill=(0, 0, 255), width=2)
xy = width // 2 - 60, height // 2 - 60, width // 2 + 60, height // 2 + 60
# 通过ImageDraw对象的rectangle方法绘制矩形
drawer.rectangle(xy, outline=(255, 0, 0), width=2)
# 通过ImageDraw对象的ellipse方法绘制椭圆
for i in range(4):
left, top, right, bottom = 150 + i * 120, 220, 310 + i * 120, 380
drawer.ellipse((left, top, right, bottom), outline=random_color(), width=8)
# 显示图像
image.show()
# 保存图像
image.save('result.png')
```
> **注意**:上面代码中使用的字体文件需要根据自己准备,可以选择自己喜欢的字体文件并放置在代码目录下。
### 简单的总结
使用Python语言做开发除了可以用Pillow来处理图像外还可以使用更为强大的OpenCV库来完成图形图像的处理OpenCV**Open** Source **C**omputer **V**ision Library是一个跨平台的计算机视觉库可以用来开发实时图像处理、计算机视觉和模式识别程序。在我们的日常工作中有很多繁琐乏味的任务其实都可以通过Python程序来处理编程的目的就是让计算机帮助我们解决问题减少重复乏味的劳动。通过本章节的学习相信大家已经感受到了使用Python程序绘图P图的乐趣其实Python能做的事情还远不止这些继续你的学习吧。

View File

@ -0,0 +1,201 @@
## 第29课用Python发送邮件和短信
在前面的课程中我们已经教会大家如何用Python程序自动的生成Excel、Word、PDF文档接下来我们还可以更进一步就是通过邮件将生成好的文档发送给指定的收件人然后用短信告知对方我们发出了邮件。这些事情利用Python程序也可以轻松愉快的解决。
### 发送电子邮件
在即时通信软件如此发达的今天,电子邮件仍然是互联网上使用最为广泛的应用之一,公司向应聘者发出录用通知、网站向用户发送一个激活账号的链接、银行向客户推广它们的理财产品等几乎都是通过电子邮件来完成的,而这些任务应该都是由程序自动完成的。
我们可以用HTTP超文本传输协议来访问网站资源HTTP是一个应用级协议它建立在TCP传输控制协议之上TCP为很多应用级协议提供了可靠的数据传输服务。如果要发送电子邮件需要使用SMTP简单邮件传输协议它也是建立在TCP之上的应用级协议规定了邮件的发送者如何跟邮件服务器进行通信的细节。Python通过名为`smtplib`的模块将这些操作简化成了`SMTP_SSL`对象,通过该对象的`login`和`send_mail`方法,就能够完成发送邮件的操作。
我们先尝试一下发送一封极为简单的邮件,该邮件不包含附件、图片以及其他超文本内容。发送邮件首先需要接入邮件服务器,我们可以自己架设邮件服务器,这件事情对新手并不友好,但是我们可以选择使用第三方提供的邮件服务。例如,我在<www.126.com>已经注册了账号登录成功之后就可以在设置中开启SMTP服务这样就相当于获得了邮件服务器具体的操作如下所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820190307.png" alt="image-20210820190306861" width="95%">
![image-20210820190816557](https://gitee.com/jackfrued/mypic/raw/master/20210820190816.png)
用手机扫码上面的二维码可以通过发送短信的方式来获取授权码,短信发送成功后,点击“我已发送”就可以获得授权码。授权码需要妥善保管,因为一旦泄露就会被其他人冒用你的身份来发送邮件。接下来,我们就可以编写发送邮件的代码了,如下所示。
```Python
import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
# 创建邮件主体对象
email = MIMEMultipart()
# 设置发件人、收件人和主题
email['From'] = 'xxxxxxxxx@126.com'
email['To'] = 'yyyyyy@qq.com;zzzzzz@1000phone.com'
email['Subject'] = Header('上半年工作情况汇报', 'utf-8')
# 添加邮件正文内容
content = """据德国媒体报道当地时间9日德国火车司机工会成员进行了投票
定于当地时间10日起进行全国性罢工货运交通方面的罢工已于当地时间10日19时开始。
此后从11日凌晨2时到13日凌晨2时德国全国范围内的客运和铁路基础设施将进行48小时的罢工。"""
# 创建SMTP_SSL对象连接邮件服务器
smtp_obj = smtplib.SMTP_SSL('smtp.126.com', 465)
# 通过用户名和授权码进行登录
smtp_obj.login('xxxxxxxxx@126.com', '邮件服务器的授权码')
# 发送邮件(发件人、收件人、邮件内容(字符串))
smtp_obj.sendmail(
'xxxxxxxxx@126.com',
['yyyyyy@qq.com', 'zzzzzz@1000phone.com'],
email.as_string()
)
```
如果要发送带有附件的邮件只需要将附件的内容处理成BASE64编码那么它就和普通的文本内容几乎没有什么区别。BASE64是一种基于64个可打印字符来表示二进制数据的表示方法常用于某些需要表示、传输、存储二进制数据的场合电子邮件就是其中之一。对这种编码方式不理解的同学推荐阅读[《Base64笔记》](http://www.ruanyifeng.com/blog/2008/06/base64.html)一文。在之前的内容中我们也提到过Python标准库的`base64`模块提供了对BASE64编解码的支持。
下面的代码演示了如何发送带附件的邮件。
```Python
import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from urllib.parse import quote
# 创建邮件主体对象
email = MIMEMultipart()
# 设置发件人、收件人和主题
email['From'] = 'xxxxxxxxx@126.com'
email['To'] = 'zzzzzzzz@1000phone.com'
email['Subject'] = Header('请查收离职证明文件', 'utf-8')
# 添加邮件正文内容带HTML标签排版的内容
content = """<p>亲爱的前同事:</p>
<p>你需要的离职证明在附件中,请查收!</p>
<br>
<p>祝,好!</p>
<hr>
<p>孙美丽 即日</p>"""
email.attach(MIMEText(content, 'html', 'utf-8'))
# 读取作为附件的文件
with open(f'resources/王大锤离职证明.docx', 'rb') as file:
attachment = MIMEText(file.read(), 'base64', 'utf-8')
# 指定内容类型
attachment['content-type'] = 'application/octet-stream'
# 将中文文件名处理成百分号编码
filename = quote('王大锤离职证明.docx')
# 指定如何处置内容
attachment['content-disposition'] = f'attachment; filename="{filename}"'
# 创建SMTP_SSL对象连接邮件服务器
smtp_obj = smtplib.SMTP_SSL('smtp.126.com', 465)
# 通过用户名和授权码进行登录
smtp_obj.login('xxxxxxxxx@126.com', '邮件服务器的授权码')
# 发送邮件(发件人、收件人、邮件内容(字符串))
smtp_obj.sendmail(
'xxxxxxxxx@126.com',
'zzzzzzzz@1000phone.com',
email.as_string()
)
```
为了方便大家用Python实现邮件发送我将上面的代码封装成了函数使用的时候大家只需要调整邮件服务器域名、端口、用户名和授权码就可以了。
```Python
import smtplib
from email.header import Header
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from urllib.parse import quote
# 邮件服务器域名(自行修改)
EMAIL_HOST = 'smtp.126.com'
# 邮件服务端口通常是465
EMAIL_PORT = 465
# 登录邮件服务器的账号(自行修改)
EMAIL_USER = 'xxxxxxxxx@126.com'
# 开通SMTP服务的授权码自行修改
EMAIL_AUTH = '邮件服务器的授权码'
def send_email(*, from_user, to_users, subject='', content='', filenames=[]):
"""发送邮件
:param from_user: 发件人
:param to_users: 收件人,多个收件人用英文分号进行分隔
:param subject: 邮件的主题
:param content: 邮件正文内容
:param filenames: 附件要发送的文件路径
"""
email = MIMEMultipart()
email['From'] = from_user
email['To'] = to_users
email['Subject'] = subject
message = MIMEText(content, 'plain', 'utf-8')
email.attach(message)
for filename in filenames:
with open(filename, 'rb') as file:
pos = filename.rfind('/')
display_filename = filename[pos + 1:] if pos >= 0 else filename
display_filename = quote(display_filename)
attachment = MIMEText(file.read(), 'base64', 'utf-8')
attachment['content-type'] = 'application/octet-stream'
attachment['content-disposition'] = f'attachment; filename="{display_filename}"'
email.attach(attachment)
smtp = smtplib.SMTP_SSL(EMAIL_HOST, EMAIL_PORT)
smtp.login(EMAIL_USER, EMAIL_AUTH)
smtp.sendmail(from_user, to_users, email.as_string())
```
### 发送短信
发送短信也是项目中常见的功能,网站的注册码、验证码、营销信息基本上都是通过短信来发送给用户的。发送短信需要三方平台的支持,下面我们以[螺丝帽平台](https://luosimao.com/)为例为大家介绍如何用Python程序发送短信。注册账号和购买短信服务的细节我们不在这里进行赘述大家可以咨询平台的客服。
![image-20210820194420911](https://gitee.com/jackfrued/mypic/raw/master/20210820194421.png)
接下来,我们可以通过`requests`库向平台提供的短信网关发起一个HTTP请求通过将接收短信的手机号和短信内容作为参数就可以发送短信代码如下所示。
```Python
import random
import requests
def send_message_by_luosimao(tel, message):
"""发送短信(调用螺丝帽短信网关)"""
resp = requests.post(
url='http://sms-api.luosimao.com/v1/send.json',
auth=('api', 'key-注册成功后平台分配的KEY'),
data={
'mobile': tel,
'message': message
},
timeout=10,
verify=False
)
return resp.json()
def gen_mobile_code(length=6):
"""生成指定长度的手机验证码"""
return ''.join(random.choices('0123456789', k=length))
def main():
code = gen_mobile_code()
message = f'您的短信验证码是{code}打死也不能告诉别人哟【Python小课】'
print(send_message_by_luosimao('13500112233', message))
if __name__ == '__main__':
main()
```
上面请求螺丝帽的短信网关`http://sms-api.luosimao.com/v1/send.json`会返回JSON格式的数据如果返回`{'error': 0, 'msg': 'OK'}`就说明短信已经发送成功了,如果`error`的值不是`0`,可以通过查看官方的[开发文档](https://luosimao.com/docs/api/)了解到底哪个环节出了问题。螺丝帽平台常见的错误类型如下图所示。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210820195505.png" alt="image-20210820195505761" style="zoom:50%;">
目前大多数短信平台都会要求短信内容必须附上签名下图是我在螺丝帽平台配置的短信签名“【Python小课】”。有些涉及到敏感内容的短信还需要提前配置短信模板有兴趣的读者可以自行研究。一般情况下平台为了防范短信被盗用还会要求设置“IP白名单”不清楚如何配置的可以咨询平台客服。
![image-20210820194653785](https://gitee.com/jackfrued/mypic/raw/master/20210820194653.png)
当然国内的短信平台很多,读者可以根据自己的需要进行选择(通常会考虑费用预算、短信达到率、使用的难易程度等指标),如果需要在商业项目中使用短信服务建议购买短信平台提供的套餐服务。
### 简单的总结
其实,发送邮件和发送短信一样,也可以通过调用三方服务来完成,在实际的商业项目中,建议自己架设邮件服务器或购买三方服务来发送邮件,这个才是比较靠谱的选择。

View File

@ -0,0 +1,159 @@
## 第30课正则表达式的应用
### 正则表达式相关知识
在编写处理字符串的程时,经常会遇到在一段文本中查找符合某些规则的字符串的需求,正则表达式就是用于描述这些规则的工具,换句话说,我们可以使用正则表达式来定义字符串的匹配模式,即如何检查一个字符串是否有跟某种模式匹配的部分或者从一个字符串中将与模式匹配的部分提取出来或者替换掉。
举一个简单的例子如果你在Windows操作系统中使用过文件查找并且在指定文件名时使用过通配符`*`和`?`),那么正则表达式也是与之类似的用 来进行文本匹配的工具,只不过比起通配符正则表达式更强大,它能更精确地描述你的需求,当然你付出的代价是书写一个正则表达式比使用通配符要复杂得多,因为任何给你带来好处的东西都需要你付出对应的代价。
再举一个例子我们从某个地方可能是一个文本文件也可能是网络上的一则新闻获得了一个字符串希望在字符串中找出手机号和座机号。当然我们可以设定手机号是11位的数字注意并不是随机的11位数字因为你没有见过“25012345678”这样的手机号而座机号则是类似于“区号-号码”这样的模式如果不使用正则表达式要完成这个任务就会比较麻烦。最初计算机是为了做数学运算而诞生的处理的信息基本上都是数值而今天我们在日常工作中处理的信息很多都是文本数据我们希望计算机能够识别和处理符合某些模式的文本正则表达式就显得非常重要了。今天几乎所有的编程语言都提供了对正则表达式操作的支持Python通过标准库中的`re`模块来支持正则表达式操作。
关于正则表达式的相关知识,大家可以阅读一篇非常有名的博文叫[《正则表达式30分钟入门教程》](https://deerchao.net/tutorials/regex/regex.htm),读完这篇文章后你就可以看懂下面的表格,这是我们对正则表达式中的一些基本符号进行的扼要总结。
| 符号 | 解释 | 示例 | 说明 |
| -------------- | -------------------------------- | ------------------ | ------------------------------------------------------------ |
| `.` | 匹配任意字符 | `b.t` | 可以匹配bat / but / b#t / b1t等 |
| `\w` | 匹配字母/数字/下划线 | `b\wt` | 可以匹配bat / b1t / b_t等<br>但不能匹配b#t |
| `\s` | 匹配空白字符(包括\r、\n、\t等 | `love\syou` | 可以匹配love you |
| `\d` | 匹配数字 | `\d\d` | 可以匹配01 / 23 / 99等 |
| `\b` | 匹配单词的边界 | `\bThe\b` | |
| `^` | 匹配字符串的开始 | `^The` | 可以匹配The开头的字符串 |
| `$` | 匹配字符串的结束 | `.exe$` | 可以匹配.exe结尾的字符串 |
| `\W` | 匹配非字母/数字/下划线 | `b\Wt` | 可以匹配b#t / b@t等<br>但不能匹配but / b1t / b_t等 |
| `\S` | 匹配非空白字符 | `love\Syou` | 可以匹配love#you等<br>但不能匹配love you |
| `\D` | 匹配非数字 | `\d\D` | 可以匹配9a / 3# / 0F等 |
| `\B` | 匹配非单词边界 | `\Bio\B` | |
| `[]` | 匹配来自字符集的任意单一字符 | `[aeiou]` | 可以匹配任一元音字母字符 |
| `[^]` | 匹配不在字符集中的任意单一字符 | `[^aeiou]` | 可以匹配任一非元音字母字符 |
| `*` | 匹配0次或多次 | `\w*` | |
| `+` | 匹配1次或多次 | `\w+` | |
| `?` | 匹配0次或1次 | `\w?` | |
| `{N}` | 匹配N次 | `\w{3}` | |
| `{M,}` | 匹配至少M次 | `\w{3,}` | |
| `{M,N}` | 匹配至少M次至多N次 | `\w{3,6}` | |
| `|` | 分支 | `foo|bar` | 可以匹配foo或者bar |
| `(?#)` | 注释 | | |
| `(exp)` | 匹配exp并捕获到自动命名的组中 | | |
| `(?<name>exp)` | 匹配exp并捕获到名为name的组中 | | |
| `(?:exp)` | 匹配exp但是不捕获匹配的文本 | | |
| `(?=exp)` | 匹配exp前面的位置 | `\b\w+(?=ing)` | 可以匹配I'm dancing中的danc |
| `(?<=exp)` | 匹配exp后面的位置 | `(?<=\bdanc)\w+\b` | 可以匹配I love dancing and reading中的第一个ing |
| `(?!exp)` | 匹配后面不是exp的位置 | | |
| `(?<!exp)` | 匹配前面不是exp的位置 | | |
| `*?` | 重复任意次,但尽可能少重复 | `a.*b`<br>`a.*?b` | 将正则表达式应用于aabab前者会匹配整个字符串aabab后者会匹配aab和ab两个字符串 |
| `+?` | 重复1次或多次但尽可能少重复 | | |
| `??` | 重复0次或1次但尽可能少重复 | | |
| `{M,N}?` | 重复M到N次但尽可能少重复 | | |
| `{M,}?` | 重复M次以上但尽可能少重复 | | |
> **说明:** 如果需要匹配的字符是正则表达式中的特殊字符,那么可以使用`\`进行转义处理,例如想匹配小数点可以写成`\.`就可以了,因为直接写`.`会匹配任意字符;同理,想匹配圆括号必须写成`\(`和`\)`,否则圆括号被视为正则表达式中的分组。
### Python对正则表达式的支持
Python提供了`re`模块来支持正则表达式相关操作,下面是`re`模块中的核心函数。
| 函数 | 说明 |
| ---------------------------------------------- | ------------------------------------------------------------ |
| `compile(pattern, flags=0)` | 编译正则表达式返回正则表达式对象 |
| `match(pattern, string, flags=0)` | 用正则表达式匹配字符串 成功返回匹配对象 否则返回`None` |
| `search(pattern, string, flags=0)` | 搜索字符串中第一次出现正则表达式的模式 成功返回匹配对象 否则返回`None` |
| `split(pattern, string, maxsplit=0, flags=0)` | 用正则表达式指定的模式分隔符拆分字符串 返回列表 |
| `sub(pattern, repl, string, count=0, flags=0)` | 用指定的字符串替换原字符串中与正则表达式匹配的模式 可以用`count`指定替换的次数 |
| `fullmatch(pattern, string, flags=0)` | `match`函数的完全匹配(从字符串开头到结尾)版本 |
| `findall(pattern, string, flags=0)` | 查找字符串所有与正则表达式匹配的模式 返回字符串的列表 |
| `finditer(pattern, string, flags=0)` | 查找字符串所有与正则表达式匹配的模式 返回一个迭代器 |
| `purge()` | 清除隐式编译的正则表达式的缓存 |
| `re.I` / `re.IGNORECASE` | 忽略大小写匹配标记 |
| `re.M` / `re.MULTILINE` | 多行匹配标记 |
> **说明:** 上面提到的`re`模块中的这些函数,实际开发中也可以用正则表达式对象(`Pattern`对象)的方法替代对这些函数的使用,如果一个正则表达式需要重复的使用,那么先通过`compile`函数编译正则表达式并创建出正则表达式对象无疑是更为明智的选择。
下面我们通过一系列的例子来告诉大家在Python中如何使用正则表达式。
#### 例子1验证输入用户名和QQ号是否有效并给出对应的提示信息。
```Python
"""
要求用户名必须由字母、数字或下划线构成且长度在6~20个字符之间QQ号是5~12的数字且首位不能为0
"""
import re
username = input('请输入用户名: ')
qq = input('请输入QQ号: ')
# match函数的第一个参数是正则表达式字符串或正则表达式对象
# match函数的第二个参数是要跟正则表达式做匹配的字符串对象
m1 = re.match(r'^[0-9a-zA-Z_]{6,20}$', username)
if not m1:
print('请输入有效的用户名.')
# fullmatch函数要求字符串和正则表达式完全匹配
# 所以正则表达式没有写起始符和结束符
m2 = re.fullmatch(r'[1-9]\d{4,11}', qq)
if not m2:
print('请输入有效的QQ号.')
if m1 and m2:
print('你输入的信息是有效的!')
```
> **提示:** 上面在书写正则表达式时使用了“原始字符串”的写法(在字符串前面加上了`r`),所谓“原始字符串”就是字符串中的每个字符都是它原始的意义,说得更直接一点就是字符串中没有所谓的转义字符啦。因为正则表达式中有很多元字符和需要进行转义的地方,如果不使用原始字符串就需要将反斜杠写作`\\`,例如表示数字的`\d`得书写成`\\d`,这样不仅写起来不方便,阅读的时候也会很吃力。
#### 例子2从一段文字中提取出国内手机号码。
下面这张图是截止到2017年底国内三家运营商推出的手机号段。
<img src="https://gitee.com/jackfrued/mypic/raw/master/20210803203134.png">
```Python
import re
# 创建正则表达式对象,使用了前瞻和回顾来保证手机号前后不应该再出现数字
pattern = re.compile(r'(?<=\D)1[34578]\d{9}(?=\D)')
sentence = '''重要的事情说8130123456789遍我的手机号是13512346789这个靓号
不是15600998765也是110或119王大锤的手机号才是15600998765。'''
# 方法一:查找所有匹配并保存到一个列表中
tels_list = re.findall(pattern, sentence)
for tel in tels_list:
print(tel)
print('--------华丽的分隔线--------')
# 方法二:通过迭代器取出匹配对象并获得匹配的内容
for temp in pattern.finditer(sentence):
print(temp.group())
print('--------华丽的分隔线--------')
# 方法三通过search函数指定搜索位置找出所有匹配
m = pattern.search(sentence)
while m:
print(m.group())
m = pattern.search(sentence, m.end())
```
> **说明:** 上面匹配国内手机号的正则表达式并不够好因为像14开头的号码只有145或147而上面的正则表达式并没有考虑这种情况要匹配国内手机号更好的正则表达式的写法是`(?<=\D)(1[38]\d{9}|14[57]\d{8}|15[0-35-9]\d{8}|17[678]\d{8})(?=\D)`国内好像已经有19和16开头的手机号了但是这个暂时不在我们考虑之列。
#### 例子3替换字符串中的不良内容
```Python
import re
sentence = 'Oh, shit! 你是傻逼吗? Fuck you.'
purified = re.sub('fuck|shit|[傻煞沙][比笔逼叉缺吊碉雕]',
'*', sentence, flags=re.IGNORECASE)
print(purified) # Oh, *! 你是*吗? * you.
```
> **说明:**` re`模块的正则表达式相关函数中都有一个`flags`参数它代表了正则表达式的匹配标记可以通过该标记来指定匹配时是否忽略大小写、是否进行多行匹配、是否显示调试信息等。如果需要为flags参数指定多个值可以使用[按位或运算符](http://www.runoob.com/python/python-operators.html#ysf5)进行叠加,如`flags=re.I | re.M`。
#### 例子4拆分长字符串
```Python
import re
poem = '窗前明月光,疑是地上霜。举头望明月,低头思故乡。'
sentences_list = re.split(r'[,。]', poem)
sentences_list = [sentence for sentence in sentences_list if sentence]
for sentence in sentences_list:
print(sentence)
```
### 简单的总结
正则表达式在字符串的处理和匹配上真的非常强大,通过上面的例子相信大家已经感受到了正则表达式的魅力,当然写一个正则表达式对新手来说并不是那么容易,但是很多事情都是熟能生巧,大胆的去尝试就行了,有一个在线的[正则表达式测试工具](https://c.runoob.com/front-end/854)相信能够在一定程度上帮到大家。

View File

@ -1,89 +0,0 @@
## 第030课用Python获取网络数据
对于Python语言来说一个较为擅长的领域就是网络数据采集实现网络数据采集的程序通常称之为网络爬虫或蜘蛛程序。即便是在大数据时代数据对于中小企业来说仍然是硬伤和短板有些数据需要通过开放或付费的数据接口来获得其他的行业数据则必须要通过网络数据采集的方式来获得。不管使用哪种方式获取网络数据资源Python语言都是非常好的选择因为Python的标准库和三方库都对获取网络数据提供了良好的支持。
### HTTP和requests库
要使用Python获取网络数据我们可以先安装一个名为`requests` 的三方库这个库我们在第24课中已经使用过了。按照官方网站的解释`requests`是基于Python标准库进行了封装简化了通过[HTTP](https://www.ruanyifeng.com/blog/2016/08/http.html)访问网络资源的操作。说到HTTP相信大家不会陌生通常我们打开浏览器浏览网页时我们就是使用了HTTP或[HTTPS](https://zhuanlan.zhihu.com/p/100591696)。HTTP是一个请求响应式的协议当我们在浏览器中输入正确的[URL](https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_URL)通常也称为网址并按下回车Enter我们就向网络上的[Web服务器](https://developer.mozilla.org/zh-CN/docs/Learn/Common_questions/What_is_a_web_server)发送了一个HTTP请求服务器在收到请求后会给我们一个HTTP响应服务器给浏览器的数据就包含在这个响应中。我们可以使用浏览器提供的“开发者工具”或是“抓包工具”Fiddler、Charles等来了解HTTP请求和响应到底是什么样子的如下图所示。
![](res/http-request-response.png)
通过`requests`库我们可以让程序向浏览器一样向Web服务器发起请求并接收到服务器返回的响应从响应中我们就可以提取出我们想要的数据。下面通过两个例子来演示如何获取网页代码和网络资源图片浏览器呈现给我们的网页是用[HTML](https://developer.mozilla.org/zh-CN/docs/Web/HTML)编写的浏览器相当于是HTML的解释器环境我们看到的网页中的内容都包含在HTML的标签中。在获取到HTML代码后就可以从标签的属性或标签体中提取我们需要的内容。
获取搜狐网首页。
```Python
import requests
# requests的get函数会返回一个Response对象
resp = requests.get('https://www.sohu.com/')
if resp.status_code == 200:
# 通过Response对象的text属性获取服务器返回的文本内容
print(resp.text)
```
获取百度Logo并保存到名为`baidu.png`的本地文件中。首先在百度的首页上右键点击百度Logo并通过“复制图片地址”菜单获取图片的URL。
```Python
import requests
resp = requests.get('https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png')
with open('baidu.png', 'wb') as file:
# 通过Response对象的content属性获取服务器返回的二进制内容
file.write(resp.content)
```
> **说明**:关于`requests`库的详细使用方法,大家可以参考[官方文档](https://requests.readthedocs.io/zh_CN/latest/)的内容。
### 访问网络数据接口
国内外的很多网站都提供了开放数据接口,在开发商业项目时,如果有些事情我们自己无法解决,就可以借助这些开放的数据接口来处理。例如要根据用户或企业上传的资料进行实名认证或企业认证,我们就可以调用第三方提供的开放接口来识别用户或企业信息的真伪;例如要获取某个城市的天气信息,我们不可能直接从气象卫星拿到数据然后自己进行运算,只能通过第三方提供的数据接口来得到对应的天气信息。通常,提供有商业价值的数据的接口都是需要支付费用后才能访问的,在访问接口时还需要提供身份标识,便于服务器判断用户是不是付费用户以及进行费用扣除等相关操作。当然,还有些接口是可以免费使用的,但是必须先提供个人或者公司的信息才能访问,例如:[深圳市政府数据开放平台](https://opendata.sz.gov.cn/data/api/toApi)、[蜻蜓FM开放平台](https://open.qingting.fm/)等。如果查找自己需要的数据接口,可以访问[聚合数据](https://www.juhe.cn/)这类型的网站。
目前我们访问的网络数据接口大多会返回JSON格式的数据我们在第24课讲解序列化和反序列的时候提到过JSON格式的字符串跟Python中的字典如何进行转换并以[天行数据](https://www.tianapi.com/)为例讲解过网络数据接口访问的相关知识,这里我们就不再进行赘述了。
### 开发爬虫/蜘蛛程序
有的时候,我们需要的数据并不能通过开放数据接口来获得,但是可能在某些网页上能够获取到,这个时候就需要我们开发爬虫程序通过爬取页面来获得需要的内容。我们可以按照上面提供的方法,使用`requests`先获取到网页的HTML代码我们可以将整个代码看成一个长字符串这样我们就可以使用正则表达式的捕获组从字符串提取我们需要的内容。下面我们通过代码演示如何从[豆瓣电影](https://movie.douban.com/)获取排前250名的电影的名称。豆瓣电影Top250页面的结构和对应的代码如下图所示。
![](res/douban-movie-top250.png)
```Python
import random
import re
import time
import requests
for page in range(1, 11):
resp = requests.get(
# 请求https://movie.douban.com/top250时start参数代表了从哪一部电影开始
url=f'https://movie.douban.com/top250?start={(page - 1) * 25}',
# 如果不设置HTTP请求头中的User-Agent豆瓣会检测出爬虫程序而阻止我们的请求
# User-Agent可以设置为浏览器的标识可以在浏览器的开发者工具查看HTTP请求头找到
# 由于豆瓣网允许百度爬虫获取它的数据,因此直接将我们的爬虫伪装成百度的爬虫
headers={'User-Agent': 'BaiduSpider'}
)
# 创建正则表达式对象通过捕获组捕获span标签中的电影标题
pattern = re.compile(r'\<span class="title"\>([^&]*?)\<\/span\>')
# 通过正则表达式获取class属性为title且标签内容不以&符号开头的span标签
results = pattern.findall(resp.text)
# 循环变量列表中所有的电影标题
for result in results:
print(result)
# 随机休眠1-3秒避免获取页面过于频繁
time.sleep(random.randint(1, 3))
```
编写爬虫程序比较重要的一点就是让爬虫程序隐匿自己的身份,因为一般的网站都比较反感爬虫。隐匿身份除了像上面的代码中修改`User-Agent`之外,还可以使用**商业IP代理**(如:[蘑菇代理](http://www.moguproxy.com/)、[芝麻代理](http://www.zhimaruanjian.com/)等让被爬取的网站无法得知爬虫程序的真实IP地址也就无法通过IP地址对爬虫程序进行封禁。当然爬虫本身也是一个处于灰色地带的东西没有谁说它是违法的但也没有谁说它是合法的本着**法不禁止即为许可**的精神,我们可以根据自己工作的需要去编写爬虫程序,但是如果被爬取的网站能够**举证你的爬虫程序有破坏动产的行为**,那么在打官司的时候,基本上是要败诉的,这一点需要注意。
另外,用编写正则表达式的方式从网页中提取内容虽然可行,但是写出一个能够满足需求的正则表达式本身也不是件容易的事情,这一点对于新手来说尤为明显。在下一节课中,我们将会为大家介绍另外两种从页面中提取数据的方法,虽然从性能上来讲,它们可能不如正则表达式,但是却降低了编码的复杂性,相信大家会喜欢上它们的。
### 简单的总结
Python语言能做的事情真的很多就获取网络数据这一项而言Python几乎是一枝独秀的大量的企业和个人都会使用Python从网络上获取自己需要的数据相信这一点在现在或者将来也会是你工作中的一部分。
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,161 +0,0 @@
## 第031课用Python解析HTML页面
在上一课中我们讲到了使用Python获取网络资源如果我们获取到一个或多个页面需要从页面中提取出指定的信息首先得掌握解析HTML页面的技术。上一课中我们把整个HTML页面当成一个字符串使用正则表达式的捕获组提取出了需要的内容。但是写出一个正确的正则表达式经常也是一件让人头疼的事情。为此我们可以先了解HTML页面的结构在此基础上就可以掌握其他的解析HTML页面的方法。
### HTML页面的结构
我们在浏览器中打开任意一个网站然后通过鼠标右键菜单选择“显示网页源代码”菜单项就可以看到网页对应的HTML代码。
![](res/html_page_code.png)
代码的第1行是文档类型声明第2行的`<html>`标签是整个页面根标签的开始标签,最后一行是根标签的结束标签`</html>`。`<html>`标签下面有两个子标签`<head>`和`<body>`,放在`<body>`标签下的内容会显示在浏览器窗口中,这部分内容是网页的主体;放在`<head>`标签下的内容不会显示在浏览器窗口中但是却包含了页面重要的元信息通常称之为网页的头部。HTML页面大致的代码结构如下所示。
```HTML
<!doctype html>
<html>
<head>
<!-- 页面的元信息,如字符编码、标题、关键字、媒体查询等 -->
</head>
<body>
<!-- 页面的主体,显示在浏览器窗口中的内容 -->
</body>
</html>
```
标签、层叠样式表CSS、JavaScript是构成HTML页面的三要素其中标签用来承载页面要显示的内容CSS负责对页面的渲染而JavaScript用来控制页面的交互式行为。对HTML页面的解析可以使用一种名为XPath的语法根据HTML标签的层次结构提取标签中的内容或标签属性除此之外也可以使用CSS选择器来定位页面元素如果不清楚什么是CSS选择器可以移步到我的[《Web前端概述》](https://github.com/jackfrued/Python-100-Days/blob/master/Day21-30/21-30.Web%E5%89%8D%E7%AB%AF%E6%A6%82%E8%BF%B0.md)一文进行了解。
### XPath解析
XPath是在XMLeXtensible Markup Language文档中查找信息的一种语法XML跟HTML类似也是一种用标签承载数据的标签语言不同之处在于XML的标签是可扩展的可以自定义的而且XML对语法有更严格的要求。XPath使用路径表达式来选取XML文档中的节点或者节点集这里所说的节点包括元素、属性、文本、命名空间、处理指令、注释、根节点等。下面我们通过一个例子来说明如何使用XPath对页面进行解析。
```XML
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
<book>
<title lang="eng">Harry Potter</title>
<price>29.99</price>
</book>
<book>
<title lang="eng">Learning XML</title>
<price>39.95</price>
</book>
</bookstore>
```
对于上面的XML文件我们可以用如下所示的XPath语法获取文档中的节点。
| 路径表达式 | 结果 |
| --------------- | ------------------------------------------------------------ |
| /bookstore | 选取根元素 bookstore。**注意**:假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径! |
| //book | 选取所有 book 子元素,而不管它们在文档中的位置。 |
| //@lang | 选取名为 lang 的所有属性。 |
| /bookstore/book[1] | 选取属于 bookstore 子元素的第一个 book 元素。 |
| /bookstore/book[last()] | 选取属于 bookstore 子元素的最后一个 book 元素。 |
| /bookstore/book[last()-1] | 选取属于 bookstore 子元素的倒数第二个 book 元素。 |
| /bookstore/book[position()<3] | bookstore book |
| //title[@lang] | 选取所有拥有名为 lang 的属性的 title 元素。 |
| //title[@lang='eng'] | 选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。 |
| /bookstore/book[price>35.00] | 选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。 |
| /bookstore/book[price>35.00]/title | 选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。 |
XPath还支持通配符用法如下所示。
| 路径表达式 | 结果 |
| ------------ | --------------------------------- |
| /bookstore/* | 选取 bookstore 元素的所有子元素。 |
| //* | 选取文档中的所有元素。 |
| //title[@*] | 选取所有带有属性的 title 元素。 |
如果要选取多个节点,可以使用如下所示的方法。
| 路径表达式 | 结果 |
| -------------------------------- | ------------------------------------------------------------ |
| //book/title \| //book/price | 选取 book 元素的所有 title 和 price 元素。 |
| //title \| //price | 选取文档中的所有 title 和 price 元素。 |
| /bookstore/book/title \| //price | 选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。 |
> **说明**:上面的例子来自于“菜鸟教程”网站上的[XPath教程](<https://www.runoob.com/xpath/xpath-tutorial.html>),有兴趣的读者可以自行阅读原文。
当然如果不理解或不熟悉XPath语法可以在浏览器的开发者工具中按照如下所示的方法查看元素的XPath语法下图是在Chrome浏览器的开发者工具中查看豆瓣网电影详情信息中影片标题的XPath语法。
![](res/douban-xpath.png)
实现XPath解析需要三方库`lxml` 的支持,可以使用下面的命令安装`lxml`。
```Bash
pip install lxml
```
下面我们用XPath解析方式改写之前获取豆瓣电影Top250的代码如下所示。
```Python
import random
import time
from lxml import etree
import requests
for page in range(1, 11):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={(page - 1) * 25}',
headers={
'User-Agent': 'BaiduSpider',
}
)
tree = etree.HTML(resp.text)
# 通过XPath语法从页面中提取需要的数据
spans = tree.xpath('//*[@id="content"]/div/div[1]/ol/li/div/div[2]/div[1]/a/span[1]')
for span in spans:
print(span.text)
time.sleep(random.randint(1, 3))
```
### CSS选择器解析
对于熟悉CSS选择器和JavaScript的开发者来说通过CSS选择器获取页面元素可能是更为简单的选择因为浏览器中运行的JavaScript本身就可以`document`对象的`querySelector()`和`querySelectorAll()`方法基于CSS选择器获取页面元素。在Python中我们可以利用三方库`bs4`BeautifulSoup或`pyquery`来做同样的事情。BeautifulSoup可以用来解析HTML和XML文档修复含有未闭合标签等错误的文档通过为待解析的页面在内存中创建一棵树结构实现对从页面中提取数据操作的封装。可以用下面的命令来安装BeautifulSoup。
```Python
pip install beautifulsoup4
```
下面是使用`bs4`改写的获取豆瓣电影Top250电影名称的代码。
```Python
import random
import time
import bs4
import requests
for page in range(1, 11):
resp = requests.get(
url=f'https://movie.douban.com/top250?start={(page - 1) * 25}',
headers={
'User-Agent': 'BaiduSpider',
}
)
soup = bs4.BeautifulSoup(resp.text, 'lxml')
# 通过CSS选择器从页面中提取需要的数据
spans = soup.select('div.info > div.hd > a > span:nth-child(1)')
for span in spans:
print(span.text)
time.sleep(random.randint(1, 3))
```
关于BeautifulSoup更多的知识可以参考它的[官方网站](https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/)。
### 简单的总结
下面我们对三种解析方式做一个简单比较。
| 解析方式 | 对应的模块 | 速度 | 使用难度 |
| -------------- | ---------------- | ------ | -------- |
| 正则表达式解析 | `re` | 快 | 困难 |
| XPath解析 | `lxml` | 快 | 一般 |
| CSS选择器解析 | `bs4`或`pyquery` | 不确定 | 简单 |
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,82 +0,0 @@
## 第032课Python中的并发编程1
现如今我们使用的计算机早已是多CPU或多核的计算机为此我们使用的操作系统基本都是支持“多任务”的操作系统这样的操作系统使得我们我们可以同时运行多个程序也可以将一个程序分解为若干个相对独立的子任务让多个子任务“齐头并进”的执行从而缩短程序的执行时间同时也让用户获得更好的体验。因此当下不管用什么编程语言进行开发实现让一个程序同时执行多个任务已经成为程序员的标配技能。为此我们需要先了解两个重要的概念多进程和多线程。
### 线程和进程
我们通过操作系统运行一个程序会创建出一个或多个进程进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动。简单的说进程是操作系统分配存储空间的基本单位每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据操作系统管理所有进程的执行为它们合理的分配资源。一个进程可以通过fork或spawn的方式创建新的进程来执行其他的任务不过新的进程也有自己独立的内存空间因此两个进程如果要共享数据必须通过进程间通信机制IPC来实现具体的方式包括管道、信号、套接字等。
一个进程还可以拥有多个并发的执行线索简单的说就是拥有多个可以获得CPU调度的执行单元这就是所谓的线程。由于线程在同一个进程下它们可以共享相同的上下文因此相对于进程而言线程间的信息共享和通信更加容易。当然在单核CPU系统中多个线程不可能同时执行因为在某个时刻只有一个线程能够获得CPU多个线程通过共享了CPU执行时间的方式来达到并发的效果。在程序中使用多线程技术通常都会带来不言而喻的好处最主要的体现在提升程序的性能和改善用户体验今天我们使用的软件几乎都用到了多线程技术这一点可以利用系统自带的进程监控工具如macOS中的“活动监视器”、Windows中的“任务管理器”来证实如下图所示。
![](res/macos-monitor.png)
这里,我们还需要跟大家说说另外两个概念:**并发**concurrency和**并行**parallel。并发是指同一时刻只能有一条指令执行但是多个线程对应的指令被快速轮换地执行。比如一个处理器它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。由于处理器执行指令的速度和切换的速度非常非常快人完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作这就使得宏观上看起来多个线程在同时运行但微观上其实只有一个线程在执行。并行是指同一时刻有多条指令在多个处理器上同时执行并行必须要依赖于多个处理器不论是从宏观上还是微观上多个线程都是在同一时刻一起执行的。在我们的课程中其实并不用严格区分并发和并行两个词所以我们把Python中的多线程、多进程以及异步I/O都视为实现并发编程的手段但实际上前面两者也可以实现并行编程当然这里还有一个全局解释器锁GIL的问题我们稍后讨论。
### 多线程编程
Python标准库中`threading`模块的`Thread`类可以帮助我们非常轻松的实现多线程编程。我们用一个联网下载文件的例子来对比使用多线程和不使用多线程到底有什么区别,代码如下所示。
不使用多线程的下载。
```Python
import random
import time
def download(*, filename):
start = time.time()
print(f'开始下载{filename}.')
time.sleep(random.randint(3, 6))
print(f'{filename}下载完成.')
end = time.time()
print(f'下载耗时: {end - start:.3f}秒.')
start = time.time()
download(filename='Python从入门到住院.pdf')
download(filename='MySQL从删库到跑路.avi')
download(filename='Linux从精通到放弃.mp3')
end = time.time()
print(f'总耗时: {end - start:.3f}秒.')
```
> **说明**:上面的代码并没有真正实现联网下载的功能,而是通过`time.sleep()`休眠指定的时间模拟下载文件需要花费一段时间。
运行上面的代码,可以得到如下所示的运行结果。可以看出,当我们的程序只有一个工作线程时,每个下载任务都需要等待上一个下载任务执行结束才能开始,所以程序执行的总耗时是三个下载任务各自执行时间的总和。
```
开始下载Python从入门到住院.pdf.
Python从入门到住院.pdf下载完成.
下载耗时: 3.005秒.
开始下载MySQL从删库到跑路.avi.
MySQL从删库到跑路.avi下载完成.
下载耗时: 5.006秒.
开始下载Linux从精通到放弃.mp3.
Linux从精通到放弃.mp3下载完成.
下载耗时: 6.007秒.
总耗时: 14.018秒.
```
事实上,上面的三个下载任务之间并没有逻辑上的因果关系,三者是可以并发的,没有必要等待上一个下载任务结束,为此,我们可以使用多线程编程来改写上面的代码。
```Python
```
再次运行程序,我们可以发现,整个程序的执行时间几乎等于耗时最长的一个下载任务的执行时间,这也就意味着,三个下载任务是并发执行的,没有一个等一个,这样做很显然提高了程序的执行效率。
通过上面的代码可以看出,直接使用`Thread`类的构造器就可以创建线程对象,而线程对象的`start()`方法可以启动一个线程。线程启动后会执行`target`参数指定的函数当然前提是获得CPU的调度如果`target`指定的线程要执行的目标函数有参数,需要通过`args`参数为其进行指定。
除了上面的代码展示的创建线程的方式外,还可以通过继承`Thread`类并重写`run()`方法的方式来自定义线程,具体的代码如下所示。
```Python
```
通过上面的例子可以看出如果程序中有非常耗时的执行单元而这些耗时的执行单元之间又没有逻辑上的因果关系即B单元的执行不依赖于A单元的执行结果那么A和B两个单元就可以放到两个不同的线程中让他们并发的执行。这样做的好处除了减少程序执行的等待时间还可以带来更好的用户体验因为一个单元的阻塞不会造成程序的“假死”因为程序中还有其他的单元是可以运转的。
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,17 +0,0 @@
## 第033课Python中的并发编程2
#### 守护线程
#### 资源竞争
#### 线程池
#### GIL问题

View File

@ -1,90 +0,0 @@
## 第034课Python中的并发编程
###创建进程
### 多进程和多线程的比较
由于GIL问题CPython中的多线程是不能很好发挥多核优势的如果想要发挥多核优势可以考虑使用多进程。对于多进程的程序每个进程都有属于自己的GIL所以多进程不会受GIL的影响能够很好的发挥多核CPU的优势。对于爬虫这类I/O密集型任务来说多线程和多进程影响差别并不大。对于计算密集型任务来说多进程相比多线程在效率上会有成倍的提升。
我们通过下面的代码来为大家证实多进程的优势。
```Python
"""
time python3 example22.py
real 0m11.512s
user 0m39.319s
sys 0m0.169s
使用多进程后实际执行时间为11.512秒而用户时间39.319秒约为实际执行时间的4倍
这就证明我们的程序通过多进程使用了CPU的多核特性而且这台计算机配置了4核的CPU
"""
import concurrent.futures
import math
PRIMES = [
1116281,
1297337,
104395303,
472882027,
533000389,
817504243,
982451653,
112272535095293,
112582705942171,
112272535095293,
115280095190773,
115797848077099,
1099726899285419
] * 5
def is_prime(n):
"""判断素数"""
if n % 2 == 0:
return False
sqrt_n = int(math.floor(math.sqrt(n)))
for i in range(3, sqrt_n + 1, 2):
if n % i == 0:
return False
return True
def main():
"""主函数"""
with concurrent.futures.ProcessPoolExecutor() as executor:
for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
print('%d is prime: %s' % (number, prime))
if __name__ == '__main__':
main()
```
### 进程间通信
### 简单的总结
对于Python开发者来说以下情况需要考虑使用多线程
1. 程序需要维护许多共享的状态尤其是可变状态Python中的列表、字典、集合都是线程安全的所以使用线程而不是进程维护共享状态的代价相对较小。
2. 程序会花费大量时间在I/O操作上没有太多并行计算的需求且不需占用太多的内存。
那么在遇到下列情况时,应该考虑使用多进程:
1. 程序执行计算密集型任务(如:字节码操作、数据处理、科学计算)。
2. 程序的输入可以并行的分成块,并且可以将运算结果合并。
3. 程序在内存使用方面没有任何限制且不强依赖于I/O操作读写文件、套接字等
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,15 +0,0 @@
## 第035课Python中的并发编程4
### 异步编程
### 简单的总结
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,15 +0,0 @@
## 第036课Python中的并发编程5
### 异步编程
### 简单的总结
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。

View File

@ -1,37 +0,0 @@
## 第035课用Python程序操作MySQL
如果要在程序中实现数据持久化一个特别常见的方案就是接入关系型数据库。我们之前提到过持久化这个概念简单的说就是将内存中的数据转移到硬盘上以便长久的保存数据。接下来我们以MySQL数据库为例来讲解在Python程序中如何使用MySQL实现数据持久化我们这里不会介绍MySQL和关系型数据库的知识如果没有对应的背景知识大家可以跳过这一课或者移步到我的Python-100-Days项目中[《关系型数据库MySQL》](https://github.com/jackfrued/Python-100-Days/blob/master/Day36-40/36-38.%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93MySQL.md)一文来了解关于关系型数据库和MySQL的知识。如果想了解在Windows或macOS环境下如何安装MySQL数据库可以点击[传送门1](https://cloud.tencent.com/developer/article/1636375)或[传送门2](https://juejin.im/post/6844903831298375693)查看对应的文章。
### 准备工作
1. 创建名为`hrs`的数据库。
```SQL
```
2. 创建部门表(`tb_dept`)和员工表(`tb_emp`)。
```SQL
```
3. 插入测试数据。
```SQL
```
### Python程序接入
### 简单的总结
> **温馨提示**:学习中如果遇到困难,可以加**QQ交流群**询问。
>
> 付费群:**789050736**,群一直保留,供大家学习交流讨论问题。
>
> 免费群:**151669801**,仅供入门新手提问,定期清理群成员。