admin 管理员组

文章数量: 887021

唤醒手腕 Python 爬虫学习笔记,喜欢的同学们可以收藏下,谢谢支持。

01、基础语法知识点

字符串的分割

webString = 'www.baidu'
print(webString.split('.'))
# ['www', 'baidu', 'com']

字符串前后空格的处理,或者特殊字符的处理

webString = ' www.baidu '
print(webString.strip())

# www.baidu

webString = '!*www.baidu*!'
print(webString.strip('!*'))

# www.baidu

字符串格式化

webString = '{}www.baidu'.format('https://')
print(webString)

# https://www.baidu

自定义函数

webString = input("Please input url = ")
print(webString)


def change_number(number):
    return number.replace(number[3:7], '*'*4)


print(change_number("15916881234"))
# 159****1234

python文件通常有两种使用方法:

第一是作为脚本直接执行。

第二是 import 到其他的 python 脚本中被调用(模块重用)执行。

因此 if __name__ == 'main' 的作用就是控制这两种情况执行代码的过程,在 if __name__ == 'main' 下的代码只有在第一种情况下(即文件作为脚本直接执行)才会被执行,而 import 到其他脚本中是不会被执行。

补充一下:非常重要的知识,python字节流和字符流

字节流:介绍

程序中的输入输出都是以流的形式保存的(输入流or输出流),流中保存的实际上全都是字节(一个字节等于一个Byte占8个bit)文件。Java提供了OutputStreamInputStream两个专门操作字节流的类。

计算机能存储的唯一东西就是 bytes,为了在计算机中存储东西,我们首先得将其编码(encode),例如将其转化为 bytes

比如:要想保存音乐(以字节形式保存),我们首先得用 MP3,WAV 等将其编码;要想保存图片,我们首先得用 PNG,JPEG 等将其编码;要想保存文本,我们首先得用 ASCII,UTF-8 等将其编码。

Unicode 是字符集,不是字符编码。Unicode 把全世界的字符都搜集并且编号了,但是没有规定具体的编码规则。编码规则有 UTF-8、GBK等。

字节流:使用场景总结

字节流适合所有类型文件的数据传输,因为字节(Byte)是电脑中表示信息含义的最小单位(通常情况下一个ACSII码就是一个字节的空间来存放)。

如果是音频文件、图片、歌曲,就用字节流好,如果是关系到中文(文本)的,用字符流好。

字符流:介绍

字符流按字符(一个字符占两个字节)读数据:一次读两个字节,返回了这两个字节所对应的字符的int型数值(编码)。写入文件时把这两个字节的内容解码成这个字符在Unicode码下对应的二进制数据写入。即把原始文件中的二进制数据以字符形式读出,再将字符以二进制形式写入,所以得到的文件以字符方式存储。字符流只能处理字符或者字符串。

字符流:使用场景

字符流只能够处理纯文本(中文)数据,其他类型数据不行,但是字符流处理文本要比字节流处理文本要方便。

字节流字符流区别

  1. 字节流没有使用到缓冲区(内存),是与文件本身直接操作的。字符流使用到了缓冲区,在缓冲区的数据需要使用close()或者flush()方法强制刷新缓冲区将其输出(程序没有关闭数据是不会从缓冲区输出出来的)。

  2. 字节流在操作文件时,即使不关闭资源(close()方法),文件也能输出,但是如果字符流不使用close()方法的话,则不会输出任何内容。

  3. 在所有的硬盘上保存文件或进行传输的时候都是以字节的方法进行的,包括图片也是按字节完成,而字符是只有在内存中才会形成的,所以使用字节的操作是最多的。

  4. 字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

  5. 字节流按字节读数据,而字节不需要编码、解码,只有字节与字符之间转换时才需要编码、解码!

字节流字符流相互转换(编码解码)

字节流是最基本的,主要用在处理二进制数据,它是按字节来处理的,但实际中很多的数据是文本,所以在提出字节流的基础上又提出了字符流的概念。

字节流与字符流之间的相互转换,以python为例,将string转化成bytes首先需要进行编码(encode)。而将 bytes 转化成 string 需要进行解码(decode)。

string to bytes by utf-8 (用 utf-8 规则将字符流编码成字节流)

name = 'wristwaking'

name_utf8 = name.encode(encoding='utf-8')

type(name_utf8) # <class 'bytes'>

string to bytes by gb2312 (用 gb2312 规则将字符流编码成字节流)

name = 'cassie'

 name_gb2312 = name.encode(encoding='gb2312')

type(name_gb2312) # <class 'bytes'>

bytes to string 将 bytes 解码成 string,默认为 utf-8 规则

name_string = name_utf8.decode()

type(name_string) # <class 'str'>

bytes to string by gb2312 按 gb2312 规则将 bytes 解码成 string

name_string_gb2312 = name_gb2312.decode('gb2312')

type(name_string_gb2312) # <class 'str'>

注意:假如用utf-8进行编码,然后用gb2312进行解码的话,是不可以的。用什么编码,就要以什么方式解码。

name = "唤醒手腕"
name_utf8 = name.encode(encoding='gb2312')
print(type(name_utf8))
print(name_utf8)
# b'\xbb\xbd\xd0\xd1\xca\xd6\xcd\xf3'
name_string = name_utf8.decode(encoding='gb2312')
print(type(name_string))
print(name_string)
# 唤醒手腕

HTTP content-type

Content-Type(内容类型),一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些 PHP 网页点击的结果却是下载一个文件或一张图片的原因。

Content-Type 标头告诉客户端实际返回的内容的内容类型。

Content-Type: text/html; charset=utf-8
Content-Type: multipart/form-data; boundary=something

常见的媒体格式类型如下:

  • text/html : HTML格式
  • text/plain :纯文本格式
  • text/xml : XML格式
  • image/gif :gif图片格式
  • image/jpeg :jpg图片格式
  • image/png:png图片格式

以application开头的媒体格式类型:

  • application/xhtml+xml :XHTML格式
  • application/xml: XML数据格式
  • application/atom+xml :Atom XML聚合格式
  • application/json: JSON数据格式
  • application/pdf:pdf格式
  • application/msword : Word文档格式
  • application/octet-stream : 二进制流数据(如常见的文件下载)
  • application/x-www-form-urlencoded : 中默认的encType,form表单数据被编码为key/value格式发送到服务器(表单默认的提交数据的格式)

另外一种常见的媒体格式是上传文件之时使用的:

  • multipart/form-data : 需要在表单中进行文件上传时,就需要使用该格式

02、基本爬虫操作

网络请求加密方式:1. 对称密钥加密 2. 非对称密钥加密 3.证书加密(https)

首先安装request第三方的库

GuessedAtParserWarning: No parser was explicitly specified 未添加解析器

基本请求的案例

import requests

link = "http://www.santostang/"
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}
data = requests.get(link, headers=headers)
print(data.text)

完整代码展示

import requests
from bs4 import BeautifulSoup

link = "http://www.santostang/"
headers = {
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}
data = requests.get(link, headers=headers)

soup = BeautifulSoup(data.text, "html.parser")
print(soup.find("h1", class_="post-title").a.text)

# 第四章 – 4.3 通过selenium 模拟浏览器抓取

数据持久化的操作,写入文件中

with open('index.txt', 'w+', encoding="utf-8") as f:
    f.write(text)
    f.close()

BeautifulSoup第三方库的使用

Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。

还可以用本地 HTML 文件来创建对象:

soup = BeautifulSoup(open('index.html'))

标准选择器 find_all( name , attrs , recursive , text , ·· kwargs )

  1. find_parents() 和 find_parent()

    find_parents()返回所有祖先节点,find_parent()返回直接父节点。

  2. find_next_siblings() 和 find_next_sibling()

    find_next_siblings()返回后面所有兄弟节点,find_next_sibling()返回后面第一个兄弟节点。

  3. find_previous_siblings() 和 find_previous_sibling()

    find_previous_siblings()返回前面所有兄弟节点,find_previous_sibling()返回前面第一个兄弟节点。

  4. find_all_next() 和 find_next()

    find_all_next()返回节点后所有符合条件的节点, find_next()返回第一个符合条件的节点

  5. find_all_previous() 和 find_previous()

    find_all_previous()返回节点后所有符合条件的节点, find_previous()返回第一个符合条件的节点

可根据标签名、属性、内容来进行查找文档。find返回单个元素,find_all返回所有元素

print(soup.find_all('ul'))

for ul in soup.find_all('ul'):
    print(ul.find_all('li'))
    
print(soup.find_all('ul')[0])

print(soup.find_all(attrs={'id': 'list-1'}))
print(soup.find_all(attrs={'name': 'elements'}))

print(soup.find_all(id='list-1'))
print(soup.find_all(class_='element'))

print(soup.find_all(text='Foo'))

CSS选择器

通过select()直接传入CSS选择器即可完成选择

print(soup.select('.panel .panel-heading'))
print(soup.select('ul li'))
print(soup.select('#list-2 .element'))
print(type(soup.select('ul')[0]))

for ul in soup.select('ul'):
    print(ul.select('li'))

获取属性 与 获取内容

for ul in soup.select('ul'):
    print(ul['id'])
    print(ul.attrs['id'])

for li in soup.select('li'):
    print(li.get_text())

基本总结:

推荐使用lxml解析库,必要时使用html.parser,标签选择筛选功能弱但是速度快,建议使用find()、find_all() 查询匹配单个结果或者多个结果,如果对CSS选择器熟悉建议使用select(),记住常用的获取属性和文本值的方法

在线查词翻译的实现

import requests
import pymongo
from bs4 import BeautifulSoup

word_search = input("请输入你想要查询的单词:")

headers = {
    'User-Agent': "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"}
link = "https://www.iciba/word?w=" + word_search

data = requests.get(link, headers=headers)

soup = BeautifulSoup(data.text, features="html.parser")

with open('index.html', 'w', encoding='utf-8') as f:
    f.write(str(soup))
    f.close()

word_infor = soup.select(".Mean_part__UI9M6 li")
word = {"english": word_search, "chinese": []}
for item in word_infor:
    chinese = {item.find("i").text: []}
    for each_chinese in item.find_all("span"):
        chinese[item.find("i").text].append(each_chinese.text)
    word["chinese"].append(chinese)

print(word)

choose = input("请问是否存入Mongodb数据库:")

if choose == 'yes':
    MongoClient = pymongo.mongo_client.MongoClient("mongodb://root:root@服务器地址:27017")
    db = MongoClient["english"]
    collection = db.words
    collection.insert_one(word)
    MongoClient.close()
    print("恭喜你,存入成功!")
else:
    print("你放弃了存入操作!")

运行代码,结果如下所示:

在数据库的效果如下所示:

03、数据持久化储存

如果发生写入文件的字符出现乱码,那么需要增加 encoding="utf-8

文件操作的基础模式有三种(默认的操作模式为r模式):

  • r 模式为 read
  • w 模式为 write
  • a 模式为 append

文件读写内容的格式有两种(默认的读写内容的模式为b模式):

  • t 模式为 text
  • b 模式为 bytes

常见的文件打开模式:

r:只读模式,文件的指针放在文件开头
w:只写模式,文件不存在则创建,文件存在,则覆盖原有内容,文件指针在文件开头
a:追加模式打开文件,文件不存在则创建,文件指针在开头,文件存在则在文件尾追加内容,文件指针在源文件末尾
b:以二进制方式打开文件,不能单独使用,需要与其他模式一起使用,如:rb,wb
+:以读写模式代开文件,不能单独使用,需要与其他模式一起使用,如:a+

04、json库的使用


import requests
import json

headers = {
    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}
data = requests.get("http://localhost:8080/person/words/all", headers=headers, timeout=0.5)
json_data = json.loads(data.text)
for index, each_row in enumerate(json_data):
    print()
    print(str(index + 1) + '.\t' + each_row['english'].ljust(20) + each_row['chinese'])

# 1.	extreme             adj. 极度的,极端的;n. 极端
# 
# 2.	concern             n. 关心;关系;公司 vt. 涉及,有关;使担心
# 
# 3.	benefic             adj. 有益的

05、selenium库的使用

定位一个元素定位多个元素含义
find_element_by_idfind_elements_by_id通过元素id定位
find_element_by_namefind_elements_by_name通过元素name定位
find_element_by_xpathfind_elements_by_xpath通过xpath表达式定位
find_element_by_link_textfind_elements_by_link_text通过完整超链接定位
find_element_by_partial_link_textfind_elements_by_partial_link_text通过部分链接定位
find_element_by_tag_namefind_elements_by_tag_name通过标签定位
find_element_by_class_namefind_elements_by_class_name通过类名进行定位
find_elements_by_css_selectorfind_elements_by_css_selector通过css选择器进行定位

假如我们有一个Web页面,通过前端工具(如,Firebug)查看到一个元素的属性是这样的。

<html>
  <head></head>
  <body link="#0000cc">
    <a id="result_logo" href="/" onmousedown="return c({'fm':'tab','tab':'logo'})">
    <form id="form" class="fm" name="f" action="/s">
      <span class="soutu-btn"></span>
        <input id="kw" class="s_ipt" name="wd" value="" maxlength="255" autocomplete="off"/>
    </form>
  </body>
</html>

通过id定位:

dr.find_element_by_id("kw")

通过name定位:

dr.find_element_by_name("wd")

通过class name定位:

dr.find_element_by_class_name("s_ipt")

通过tag name定位:

dr.find_element_by_tag_name("input")

通过xpath定位,xpath定位有N种写法,这里列几个常用写法:

dr.find_element_by_xpath("//*[@id='kw']")
dr.find_element_by_xpath("//*[@name='wd']")
dr.find_element_by_xpath("//input[@class='s_ipt']")
dr.find_element_by_xpath("/html/body/form/span/input")
dr.find_element_by_xpath("//span[@class='soutu-btn']/input")
dr.find_element_by_xpath("//form[@id='form']/span/input")
dr.find_element_by_xpath("//input[@id='kw' and @name='wd']")

通过css定位,css定位有N种写法,这里列几个常用写法:

dr.find_element_by_css_selector("#kw")
dr.find_element_by_css_selector("[name=wd]")
dr.find_element_by_css_selector(".s_ipt")
dr.find_element_by_css_selector("html > body > form > span > input")
dr.find_element_by_css_selector("span.soutu-btn> input#kw")
dr.find_element_by_css_selector("form#form > span > input")

接下来,我们的页面上有一组文本链接。

<a class="mnav" href="http://news.baidu" name="tj_trnews">新闻</a>
<a class="mnav" href="http://www.hao123" name="tj_trhao123">hao123</a>

通过link text定位:

dr.find_element_by_link_text("新闻")
dr.find_element_by_link_text("hao123")

通过partial link text定位:

dr.find_element_by_partial_link_text("新")
dr.find_element_by_partial_link_text("hao")
dr.find_element_by_partial_link_text("123")

06、jieba库的使用

jieba.cut 方法接受三个输入参数: 需要分词的字符串;cut_all 参数用来控制是否采用全模式;HMM 参数用来控制是否使用 HMM 模型

jieba.cut 以及 jieba.cut_for_search 返回的结构都是一个可迭代的 generator,可以使用 for 循环来获得分词后得到的每一个词语(unicode)

print(*objects, sep=’ ‘, end=’\n’, file=sys.stdout, flush=False) print不换行,print(“helloworld”,end=“”)

import jieba

String = "My name is wrist waking"
for item in jieba.cut(String):
    print(item, end='')

String_chinese = "我的名字叫唤醒手腕"
for item in jieba.cut(String_chinese, cut_all=True):
    print(item, end=',')

# My name is wrist waking我,的,名字,叫唤,唤醒,手腕,

07、pymysql库的使用

python3 与 MySQL 进行交互编程需要安装 pymysql 库,故首先使用如下命令安装pymysql

pip install pymysql
import pymysql

# 打开数据库连接
conn = pymysql.connect(host='localhost', user="root", passwd="root", db="nodedb")
print(conn)
print(type(conn))

conn.cursor() : 获取游标
要想操作数据库,光连接数据是不够的,必须拿到操作数据库的游标,才能进行后续的操作,比如读取数据、添加数据。通过获取到的数据库连接实例conn下的cursor()方法来创建游标。

游标用来接收返回结果

说明:cursor返回一个游标实例对象,其中包含了很多操作数据的方法,比如执行sql语句。

执行sql语句execute和executemany

  • execute(query,args=None)
  • 函数作用:执行单条的sql语句,执行成功后返回受影响的行数
  • 参数说明:
  1. query:要执行的sql语句,字符串类型
  2. args:可选的序列或映射,用于query的参数值。如果args为序列,query中必须使用%s做占位符;如果args为映射,query中必须使用%(key)s做占位符
  • executemany(query,args=None)
  • 函数作用:批量执行sql语句,比如批量插入数据,执行成功后返回受影响的行数
  • 参数说明:
  1. query:要执行的sql语句,字符串类型
  2. args:嵌套的序列或映射,用于query的参数值

数据库性能瓶颈很大一部份就在于网络IO和磁盘IO,将多个sql语句放在一起,只执行一次IO,可以有效的提升数据库性能。

用executemany()方法一次性批量执行sql语句,固然很好,但是当数据一次传入过多到server端,可能造成server端的buffer溢出,也可能产生一些意想不到的麻烦。所以,合理、分批次使用executemany是个合理的办法。

定义数据连接操作的类

# connect_db:连接数据库,并操作数据库
import pymysql


class OperationMysql:
    """
    数据库SQL相关操作
    import pymysql
    # 打开数据库连接
    db = pymysql.connect("localhost","testuser","test123","TESTDB" )
    # 使用 cursor() 方法创建一个游标对象 cursor
    cursor = db.cursor()
    # 使用 execute()  方法执行 SQL 查询
    cursor.execute("SELECT VERSION()")
    """

    def __init__(self):
        # 创建一个连接数据库的对象
        self.conn = pymysql.connect(
            host='127.0.0.1',  # 连接的数据库服务器主机名
            port=3306,  # 数据库端口号
            user='root',  # 数据库登录用户名
            passwd='root',
            db='nodedb',  # 数据库名称
            charset='utf8',  # 连接编码
            cursorclass=pymysql.cursors.DictCursor
        )
        # 使用cursor()方法创建一个游标对象,用于操作数据库
        self.cur = self.conn.cursor()

    # 查询一条数据
    def search_one(self, sql):
        self.cur.execute(sql)
        result = self.cur.fetchone()  # 使用 fetchone()方法获取单条数据.只显示一行结果
        # result = self.cur.fetchall()  # 显示所有结果
        return result

    # 更新SQL
    def updata_one(self, sql):
        try:
            self.cur.execute(sql)  # 执行sql
            self.conn.commit()  # 增删改操作完数据库后,需要执行提交操作
        except:
            # 发生错误时回滚
            self.conn.rollback()
        self.conn.close()  # 记得关闭数据库连接

    # 插入SQL
    def insert_one(self, sql):
        try:
            self.cur.execute(sql)  # 执行sql
            self.conn.commit()  # 增删改操作完数据库后,需要执行提交操作
        except:
            # 发生错误时回滚
            self.conn.rollback()
        self.conn.close()

    # 删除sql
    def delete_one(self, sql):
        try:
            self.cur.execute(sql)  # 执行sql
            self.conn.commit()  # 增删改操作完数据库后,需要执行提交操作
        except:
            # 发生错误时回滚
            self.conn.rollback()
        self.conn.close()


if __name__ == '__main__':
    op_mysql = OperationMysql()
    res = op_mysql.search_one("SELECT *  from people WHERE id = 1")
    print(res)

# {'id': 1, 'name': '周杰伦', 'age': 42}

08、mongodb数据库

MongoDB是基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

MongoDB服务端可运行在Linux、Windows或mac os x平台,支持32位和64位应用,默认端口为27017。

推荐运行在64位平台,因为MongoDB在32位模式运行时支持的最大文件尺寸为2GB。

MongoDB 主要特点

  • MongoDB中的记录是一个文档,它是由字段和值对组成的数据结构。
  • 集合就是一组文档,类似于关系数据库中的表。

既然集合中可以存放任何类型的文档,那么为什么还需要使用多个集合?

这是因为所有文档都放在同一个集合中,无论对于开发者还是管理员,都很难对集合进行管理,而且这种情形下,对集合的查询等操作效率都不高。所以在实际使用中,往往将文档分类存放在不同的集合中。

MongoDB 实例可以承载多个数据库。它们之间可以看作相互独立,每个数据库都有独立的权限控制。在磁盘上,不同的数据库存放在不同的文件中。

MongoDB 中存在以下系统数据库:

  • Admin 数据库:一个权限数据库,如果创建用户的时候将该用户添加到admin 数据库中,那么该用户就自动继承了所有数据库的权限。

  • Local 数据库:这个数据库永远不会被复制,可以用来存储本地单台服务器的任意集合。

  • Config 数据库:当MongoDB使用分片模式时,config 数据库在内部使用,用于保存分片的信息。

用户权限介绍

创建用户

cd /www/server/mongodb/bin 
# mongo安装目录下的bin目录

mongo
# 启动mongo服务,输入命令行mongo,进入mongodb环境

use admin
# 切换到admin数据库
# 正常情况就会报错 Error: not authorized on admin
# 先鉴权登录 db.auth('root', '此处是密码')

db.createUser({user: "root",pwd:"root",roles:[{ role: "readWriteAnyDatabase" , db: "DBNAME" }] })
# 创建用户
db.createUser({user:"root",pwd:"123456",roles:["root"] })  # 第二种方式

# 成功结果如下:
Successfully added user: {
        "user" : "root",
        "roles" : [
                {
                        "role" : "readWriteAnyDatabase",
                        "db" : "admin"
                }
        ]
}

show users
# 查看用户列表

远程联机宝塔centos系统的Mongodb数据库

进行权限的配置修改:

宝塔面板 和 服务器平台 都要开启27017的端口号

Mongodb结合Flask框架制作接口:

import pymongo
from flask import Flask, make_response
from bson import json_util

app = Flask(__name__)
app.MongoClient = pymongo.mongo_client.MongoClient("mongodb://root:root@服务器IP:27017")


@app.route("/login")
def index():
    db = app.MongoClient["campus"]
    # 选择数据库库
    collection = db.login
    # 选择集合
    data = collection.find()
    # 查询数据
    data_res = []
    for item in data:
        data_res.append(item)
    res = make_response(json_util.dumps(data_res))
    res.status = 200
    res.headers['Content-Type'] = "application/json"
    return res

if __name__ == '__main__':
    app.run(host="0.0.0.0", port=8080, debug=True)

Object of type ‘ObjectId’ is not JSON serializable 报错

从 mongo 中直接将查到的数据 dumps 导出,报错:

Object of type 'ObjectId' is not JSON serializable

解决方法:

from bson import json_util
 
json_util.dumps(data)

有关于我的Mongodb被黑客拿脚本黑掉了的悲惨历史

上面翻译大概的意思是:

您的所有数据都是备份的,您必须向17BRyuxS53TQshpcJYKCpHDFAFcCFAnJ 48小时支付0.02 BTC才能恢复它。48小时后,我们会泄露并暴露你所有的数据。在拒绝付款的情况下,我们将与GDPR通用数据保护条例联系,并通知他们您以开放的形式存储用户数据,并且不安全。根据法律规定,您将面临重罚或逮捕,您的基地倾倒将从我们的服务器上丢弃!您可以在这里购买比特币,不需要花太多时间购买https://localbitcoins或https://buy.moonpay.io/在付款后用您的dbp在邮件中给我写信。

原因很简单,我修改的配置,bind_ip为 0.0.0.0,端口号用的默认的27017。这样很容易被扫描到,更何况我也没有设置用户名和密码,有些人设置的弱密码也很容易被破解,这就是光着身子在互联网上裸奔啊,人家写个脚本就能抓到,只能怪自己。

解决方案:

配置文件中修改bind_ip 为127.0.0.1,这样就只能允许本机访问。

端口号想改就改,改掉后避免对于默认端口27017的扫描。

nohttpinterface:是否禁止http接口,即28017 端口开启的服务。默认false,支持auth:用户认证,默认false。不需要认证。当设置为true时候,进入数据库需要auth验证,当数据库里没有用户,则不需要验证也可以操作。直到创建了第一个用户,之后操作都需要验证。

dbpath = /usr/local/mongodb/db
logpath = /logs/mongodb.log
port = 27017
fork = true
nohttpinterface=true
bind_ip = 127.0.0.1
auth = true

注意:加强Mongodb的admin密码,切勿使用:admin或root等简单密码

修改Mongodb用户的密码

db.changeUserPassword("root","mongodbadmin");

09、pymongo库的使用

远程连接宝塔面板的Mongodb

import pymongo
from datetime import datetime

print(datetime.now())
# username="test"
# password="test"
# connection=pymongo.mongo_client.MongoClient(host="192.168.10.9:27017,connect=False,username=username,password=password)

# 链接服务器, 是本地服务器可不需要传入参数
MongoClient = pymongo.mongo_client.MongoClient("mongodb://root:root@远程服务器IP:27017")
# MongoClient = pymongo.MongoClient("mongodb://root:root@远程服务器IP:27017")


# 获取数据库, 中括号中填入数据库中的名字
db = MongoClient["wrist"]


collection = db.zhangyan

res = collection.find()

for item in res:
    print(item.get("name"))

在Document中插入数据:

collection.insert_one({'name': '唤醒手腕', 'datetime': datetime.now()})

insert_many(list_of_dict) # 插入多个

比较运算符 查找数据

符号说明
$eq它将匹配等于指定值的值
$ne它将匹配所有不等于指定值的值
$gt它将匹配大于指定值的值
$gte它将匹配所有大于或等于指定值的值
$lt它将匹配所有小于指定值的值
$lte它将匹配所有小于或等于指定值的值
$in它将匹配数组中指定的任何值
$nin它讲匹配不再数组中的值
filterOption = {
    "age": {"$gte": 20}
}
# 查询年龄大于等于20岁的
data = collection.find_one(filterOption)
print(data)
# {'_id': ObjectId('61c7297dfff4db0a50af9f06'), 'name': '张燕', 'age': 20}

逻辑运算符查询

filterAnd = {
	'$and':[
		{'fid': {'$eq': 2048}}, # filter_01
		{'sid': {'$ne': 1024}}  # filter_02
		]
}

filterOr = {
	'$or':[
		{'fid': {'$eq': 2048}}, # filter_01
		{'sid': {'$ne': 1024}}  # filter_02
		]	
}

正则表达式查询

filterOption = {
	'name': {'$regex': r'Tom [a-zA-Z]+'}
}

遍历文档获取集合内所有文件:

# collection.find({})
# collection.find_one(filter) 只返回1个

for one in collection.find({}):
	print(one)

删除的操作:

删除一个 collection.delete_one(filter)

删除多个 collection.delete_many(filter)

修改的操作:

collection.update_many(filter, update)

新参量 update 说明:形式 {command: {key: value}}

  • $set 修改或新增字段
  • $unset 删除指定字段
  • $rename 重命名字段
# 修改或新增字段
filter = {'name': '马大师'}
add_data = {'age': 60}

# key name 存在,改其值为 马大师?
update1 = {'$set': {'name': '马大师?'}}

# key age 不存在,插入字段
update2 = {'$set': add_data}

collection.update_one(filter, update1)
collection.update_one(filter, update2)

# 删除指定字段
filter = {'name': '马大师?'}
del_data = {'age': 60}

update = {'$unset': del_data}

collection.update_one(filter, update)

# 重命名字段
filter = {'name': '马大师?'}
update = {'$rename': {'name': '名字'}}
collection.update_one(filter, update)

断开连接:

client.close()

综合案例:

import requests
import json
import pymongo

headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'}
data = requests.get("http://localhost:8080/person/words/all", headers=headers)
words = json.loads(data.text)

MongoClient = pymongo.mongo_client.MongoClient("mongodb://root:root@远程服务器IP:27017")
db = MongoClient['wrist']
collection = db.word
collection.insert_many(words)

10、媒体资源爬取

requests对象的get和post方法都会返回一个对象Response对象,在这个对象中存放的是服务器返回的所有信息,包括响应头,响应状态码等等。

  • .text 返回的是Beautifulsoup根据猜测的编码方式将content内容编码成字符串。
  • .content 返回的是bytes字节码

.text是现成的字符串,.content还要编码,但是.text不是所有时候显示都正常,这是就需要用.content进行手动编码。

关于图片资源的爬取

首先就是获取图片资源的URL地址,对地址发起请求获取二进制数据 data.content

import requests

url = "https://gimg2.baidu/image_search/src=http%3A%2F%2Fc-ssl.duitang%2Fuploads%2Fitem%2F202005%2F12%2F20200512131057_4Rsc8.thumb.1000_0.jpeg"

data = requests.get(url)

with open('minitang.jpg', 'wb') as f:
    f.write(data.content)
    f.close()

关于视频资源的爬取

首先就是获取视频资源的URL地址,对地址发起请求获取二进制数据 data.content

import requests

url = "https://vd3.bdstatic/mda-mmq1yv5cag635vhm/cae_h264/1640395713342120634/mda-mmq1yv5cag635vhm.mp4"

data = requests.get(url)

with open("kaoyan.mp4","wb") as f:
    f.write(data.content)
    f.close()

11、PIL库的使用

使用 PIL 之前需要 import Image 模块

PIL是python平台事实上的图像处理标准库,但PIL仅支持到python2.7,加上年久失修,于是在PIL的基础上创建了兼容的版本pillow,支持最新的python3.X。

然后你就可以使用Image.open(‘xx.bmp’) 来打开一个位图文件进行处理了。

打开文件你不用担心格式,也不用了解格式,无论什么格式,都只要把文件名丢给 Image.open 就可以了。真所谓 bmp、jpg、png、gif……,万物皆可斩。

img = Image.open(‘origin.png’)    # 得到一个图像的实例对象 img

图像处理中,最基本的就是色彩空间的转换。一般而言,我们的图像都是 RGB 色彩空间的,但在图像识别当中,我们可能需要转换图像到灰度图、二值图等不同的色彩空间。

PIL 在这方面也提供了极完备的支持,我们可以:

new_img = img.convert(‘L’)

把 img 转换为 256 级灰度图像, convert() 是图像实例对象的一个方法,接受一个 mode 参数,用以指定一种色彩模式,mode 的取值可以是如下几种:

  • 1 (1-bit pixels, black and white, stored with one pixel per byte)
  • L (8-bit pixels, black and white)
  • P (8-bit pixels, mapped to any other mode using a colour palette)
  • RGB (3x8-bit pixels, true colour)
  • RGBA (4x8-bit pixels, true colour with transparency mask)
  • CMYK (4x8-bit pixels, colour separation)
  • YCbCr (3x8-bit pixels, colour video format)
  • I (32-bit signed integer pixels)
  • F (32-bit floating point pixels)

图像增强通常用以图像识别之前的预处理,适当的图像增强能够使得识别过程达到事半功倍的效果。 PIL 在这方面提供了一个名为 ImageEnhance 的模块,提供了几种常见的图像增强方案:

import ImageEnhance

enhancer = ImageEnhance.Sharpness(image)

for i in range(8):
    factor = i / 4.0
    enhancer.enhance(factor).show("Sharpness %f" % factor)

上面的代码即是一个典型的使用 ImageEnhance 模块的例子。

SharpnessImageEnhance 模块的一个类,用以锐化图片。这一模块主要包含如下几个类:Color、BrightnessContrastSharpness,它们都有一个共同的接口 .enhance(factor),接受一个浮点参数 factor,标示增强的比例。

灰度图的制作

from PIL import Image

img = Image.open("../ImageMake/minitang.jpg")

img = img.convert('L')

try:
    img.save("L.jpg")
except IOError:
    print("cannot convert")

子图片粘贴到父图片上

from PIL import Image
import os

imgMini = Image.open('../ImageMake/L.jpg')

imgMini = imgMini.resize((300, 300))

imgBack = Image.open('../ImageMake/minitang.jpg')

imgBack = imgBack.resize((600, 600))

imgBack.paste(imgMini, (200, 200, 500, 500))

try:
    imgBack.save("bingjie.jpg")
except IOError:
    print("cannot save")

高斯模糊图制作

from PIL import Image,ImageFilter

image = Image.open("minitang.jpg")
image = image.filter(ImageFilter.GaussianBlur(radius=20))

try:
    image.save("gaoshi02.jpg")
except IOError:
    print("cannot save")

二维码制作

首先要安装qrcode库,url可以是链接地址,也可以是文本数据。

import qrcode

qr = qrcode.QRCode(
    version=None,
    error_correction=qrcode.constants.ERROR_CORRECT_Q,
    box_size=10,
    border=1,
)

url = '巩师伟你是个傻逼!'

qr.add_data(url)
qr.make(fit=True)
img = qr.make_image(fill_color="green", back_color="transparent")

print (img.pixel_size)

img.save('output2.png')

12、图像识别技术

图片的文字识别

若未安装pillow,以管理员的身份打开命令提示符,输入:pip install pillow.

例如识别如下图片中的文字,打印到控制台上:

代码如下所示:

from PIL import Image
import pytesseract

im = Image.open('imgText.png')
print(pytesseract.image_to_string(im, lang='chi_sim').replace('\n', ''))

运行Python文件,如果出现如下报错,错误原因是:没有安装识别引擎tesseract-ocr

tesseract-ocr : 链接:https://pan.baidu/s/1cu4xF8fAXfKYq71qMETeCg 提取码:5hjq

解压后,双击 tesseract-ocr-w64-setup-v4.1.0.20190314.exe 进行安装

解压安装tesseract-ocr后做如下操作,就可以支持中文识别了。因为tesseract-ocr默认不支持中文识别。

安装完成tesseract-ocr后,我们还需要做一下配置

打开目录:C:\Users\你的账户名称\AppData\Local\Programs\Python\Python39\Lib\site-packages\pytesseract

我的账号名称是16204,效果如下:,

找到pytesseract.py,打开(用pycharm打开)后做如下操作

......

# tesseract_cmd = 'tesseract'

# 将上面的注释,tesseract_cmd,重新赋值为你tesseract-ocr安装目录下tesseract.exe文件的地址值

tesseract_cmd = r'C:\Program Files\Tesseract-OCR\tesseract.exe'
# 加r是不进行转义的意思

......

以上的操作就是:关联OCR和pytesseract

至此我们所有的配置就完成了,运行下面代码就可以解析成文字了

若出现如下的错误,先不要着急,是环境变量没有配置,我们再进行环境变量配置。

pytesseract.pytesseract.TesseractError: (1, 'Error opening data file C:/Program Files/Tesseract-OCR/tessdata/chi_sim.traineddata Please make sure the TESSDATA_PREFIX environment variable is set to the parent directory of your "tessdata" directory. Failed loading language \'chi_sim\' Tesseract couldn\'t load any languages! Could not initialize tesseract.')


在进行系统变量配置

运行结果如下所示:(识别中文还是存在误差的)

唤醒ˉˉ腕你罡个 痴傻帽呀!

英文图片识别的测试

读取下面图片中的英文,打印在控制台上。

代码展示:

from PIL import Image
import pytesseract

im = Image.open('imgenglish.png')
print(pytesseract.image_to_string(im, lang='eng').replace('\n', ''))

# lang='eng' 识别语言设置为 eng (英语)
# 解释下为什么要加.replace('\n', ''),因为在文字识别时候会出现空行,所以要清除这些不必要的空行。

打印的结果如下所示:(可见英文的识别度比中文高)

Time runs fast with the rapid of my heart.

13、pypiwin32库的使用

上个小节介绍了,图像识别技术,那么这个小节,介绍下智能语音朗读

需要用到的第三方库是pypiwin32,可以到pycharm的setting下安装该第三方库

import win32com.client

speaker = win32com.client.Dispatch("SAPI.SpVoice")

speaker.Speak("唤醒手腕你真的帅呀!") # 朗读内容

运行上述的代码,可以听到智能语言朗读,不仅支持中文朗读,也支持英语朗读。

编写语音朗读闹钟功能的示例代码

首先介绍下python中关于时间的库:time,它是python环境自带的本地库,无需安装。

import time

currentTime = time.localtime() # 获取本地时间赋值给currentTime变量
print(currentTime)

# 结果:time.struct_time(tm_year=2021, tm_mon=12, tm_mday=26, tm_hour=19, tm_min=23, tm_sec=29, tm_wday=6, tm_yday=360, tm_isdst=0)

由上可见time.localtime()是有关当前系统时间的结构体。
time.struct_time.tm_year表示当前年,time.struct_time.tm_mon表示当前月······,其他表示很容易理解,不举例了。

import win32com.client
import time

speak = win32com.client.Dispatch("SAPI.SpVoice")

setUpTime = input("你要闹铃的时间(例如:19:45:40)= ")


def timeFormat(value):
    if value >= 10:
        return str(value)
    else:
        return '0' + str(value)
# 自定义 时间格式化函数


while 1:
    currentTime = time.localtime()
    # 当前时间的纪元值

    currentH = timeFormat(currentTime.tm_hour)
    currentM = timeFormat(currentTime.tm_min)
    currentS = timeFormat(currentTime.tm_sec)

    timeShow = currentH + ':' + currentM + ':' + currentS
    print('当前时间:' + timeShow)

    if timeShow == setUpTime:
        speak.Speak("请注意,请注意,It's time for homework")
        break
        
    time.sleep(1)
    # time.sleep(1) 延迟1秒

有关于datatime库具体介绍

print   '时间:(%Y-%m-%d %H:%M:%S %f): ' , dt.strftime( '%Y-%m-%d %H:%M:%S %f' )  
print   '时间:(%Y-%m-%d %H:%M:%S %p): ' , dt.strftime( '%y-%m-%d %I:%M:%S %p' )  
print   '星期缩写%%a: %s '  % dt.strftime( '%a' )  
print   '星期全拼%%A: %s '  % dt.strftime( '%A' )  
print   '月份缩写%%b: %s '  % dt.strftime( '%b' )  
print   '月份全批%%B: %s '  % dt.strftime( '%B' )  
print   '日期时间%%c: %s '  % dt.strftime( '%c' )  
print   '今天是这周的第%s天 '  % dt.strftime( '%w' )  
print   '今天是今年的第%s天 '  % dt.strftime( '%j' )  
print   '今周是今年的第%s周 '  % dt.strftime( '%U' ) 
print   '今天是当月的第%s天 '  % dt.strftime( '%d' )

14、数据可视化案例

为什么学数据可视化,对于我们在爬取到数据进行分析,那么进行数据可视化是必不可少的。如果有志于在大数据、机器学习、人工智能领域从业的话,数据可视化是首当其冲的的,因此说不得不学。

数据可视化是指通过可视化表示来探索数据,它与数据挖掘紧密相关,而数据挖掘指得是使用代码来探索数据集的规律和关联。

对于博主来说呢,博主常用Matlab进行数据可视化,我是哔哩哔哩Matlab方向的UP主,Matlab和Python都可以进行数据可视化,主要区别:Matlab比较偏向于工程计算,对于计算数学的模拟。而python来说呢,比较轻量级些,用于数据可视化是比较优越的。

数据可视化常用的第三方库:plotly、Matplotlib

如何画折线图?

import matplotlib.pyplot

plt = matplotlib.pyplot
# 演示一下,y = x^2
X_value = [1, 2, 3, 4, 5]
Y_value = [1, 4, 9, 16, 25]

plt.plot(X_value, Y_value)

plt.show()
# plt.show() 进行图像的展示

运行上述的代码,效果如下:
对数据表格进行操作代码,展示如下(我们不需要会背,理解会用就行,后期可以查文档进行制作就行)

import matplotlib.pyplot

plt = matplotlib.pyplot
# 演示一下,y = x^2
X_value = [1, 2, 3, 4, 5]
Y_value = [1, 4, 9, 16, 25]

plt.plot(X_value, Y_value)

plt.title("Squares Numbers", fontsize=24)
# 设置标题及大小,只支持英语

plt.xlabel("variable X_value", fontsize=14)
# 设置x坐标名称及大小,只支持英语

plt.ylabel("Function Squares of X_value", fontsize=14)
# 设置y坐标名称及大小,只支持英语

plt.tick_params(axis='both', labelsize=14)
# 设置坐标轴上数字大小

plt.legend(['X'], loc=1)
# 为添加线描述;loc为描述的位置,可用数字为1-10;多个标签时为['X,'Y']类推

plt.show()
# plt.show() 进行图像的展示

完善信息后的图像,展示的效果如下:

显示中文字符:matplotlib.pyplot默认是不支持中文字符的,最简单的方法就是在代码前添加如下命令:

plt.rcParams['font.sans-serif']=['SimHei']

15、matplotlib库的使用

在上个小节我们简单介绍了数据可视化简单案例,这小节我们详细去介绍matplotlib库的具体如何去使用。

matplotlib.pyplot是使matplotlib像MATLAB一样工作的命令样式函数的集合。

每个pyplot功能都会对图形进行一些更改:例如,创建图形,在图形中创建绘图区域,在绘图区域中绘制一些线条,用标签装饰绘图等。

使用pyplot生成可视化效果非常快:

import matplotlib.pyplot as plt

plt.plot([1, 2, 3, 4])
plt.ylabel('some numbers')
plt.show()

# import matplotlib.pyplot
# plt = matplotlib.pyplot
# 上面注释的两行代码简化成 import matplotlib.pyplot as plt

展示的效果如下:

如果为plot()命令提供单个列表或数组 ,则matplotlib假定它是y值的序列,并自动生成x值。由于python范围从0开始,因此默认x向量的长度与y相同,但从0开始,因此x数据为 [0, 1, 2, 3]

plot()是一个通用命令,它将接受任意数量的参数。例如,要绘制x与y的关系,可以发出以下命令:

plt.plot([1, 2, 3, 4], [1, 4, 9, 16])

格式化绘图样式

对于每对x,y参数,都有一个可选的第三个参数,它是表示图的颜色和线条类型的格式字符串。

格式字符串的字母和符号来自MATLAB,您将颜色字符串与线条样式字符串连接在一起。默认格式字符串是“ b-”,这是一条蓝色实线。例如,要用红色圆圈绘制以上内容,则需要编写代码如下:

plt.plot([1, 2, 3, 4], [1, 4, 9, 16], 'ro')
plt.axis([0, 6, 0, 20]) #[xmin, xmax, ymin, ymax]

如果matplotlib只限于使用列表,则对于数字处理将毫无用处。通常,都会使用numpy数组来配合完成。实际上,所有序列都在内部转换为numpy数组。下面的示例说明了使用数组在一条命令中绘制几行具有不同格式样式的行。

import matplotlib.pyplot as plt
import numpy as np
#numpy是python内置库,无需安装

# 返回一个数组,从0到5,间距是0.25
t = np.arange(0, 5, 0.25)

# 红色虚线,蓝色正方形,绿色三角形
plt.plot(t, t, 'r--', t, t ** 2, 'bs', t, t ** 3, 'g^')
plt.show()

展示的效果如下:

数据可视化图片大小设置与保存

import matplotlib.pyplot as plt

x = range(2, 12, 2)
y = [3, 6, 2, 7, 8]
# 设置图片大小,像素
plt.figure(figsize=(10, 4), dpi=70)
# 绘制折线图,颜色为红色,线宽为2,透明度为0.5
plt.plot(x, y, 'r', label='line 1', linewidth=2, alpha=0.5)
# 绘制蓝色点
plt.plot(x, y, 'bo')
# 保存图片 当前目录下的image.png
plt.savefig("./image.png")
# 显示图片
plt.show()

展示的效果如下:

16、数据分析词云图

如何用python进行数据分析,制作词云图,比如下面这只“猪”

我们把构建词云分为两步:

1、处理文本数据

在生成词云时,wordcloud默认会以空格或标点为分隔符对目标文本进行分词处理。

对于中文文本,分词处理需要由用户来完成。一般步骤是先将文本分词处理,然后以空格拼接,再调用wordcloud库函数。

2、产生词云图片

wordcloud库的核心是WordColoud 类,所有的功能都封装在WordCloud 类中。

使用时需要实例化一个WordCloud 类的对象,并调用其generate(text)方法将text文本转化为词云。

处理文本数据

jieba支持三种分词模式:

  1. 精确模式lcut(),试图将句子最精确地切开,适合文本分析,单词无冗余;
  2. 全模式lcut(s, cut_all=True),把句子中所有的可以成词的词语都扫描出来,速度非常快,但是不能解决歧义,存在冗余;
  3. 搜索引擎模式cut_for_search(s),在精确模式的基础上,对长词再次切分,提高召回率,适合用于搜索引擎分词。

案例展示如下:

import jieba

with open("wordCloudData.txt", "r", encoding='utf-8') as f:  # 读取我们的待处理文本
    txt = f.read()
    f.close()

remove_data = [",", "。", '\n', '\xa0', ' ']  # 无效数据
# '\xa0' 就是 HTML里的&nbsp;  
# 去除无效数据

for r_data in remove_data:
    txt = txt.replace(r_data, "")

words = jieba.lcut(txt)  # 使用精确分词模式进行分词后保存为word列表

with open("wordCloud_Save.txt", "w", encoding='utf-8') as f:
    for word in words:
        f.write(word+' ')
    print("File save successfully")

# UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 111: illegal multibyte sequence
# 如果出现上述报错:encoding='utf-8' 没加的缘故

运行成功以后,我们就可以在当前目录下生成wordCloud_Save.txt文件

产生词云图片

wordcloud类的常用方法:

  • generate(text) :由text文本生成词云
  • to_file(filename) :将词云图保存为名为filename的文件

我们要准备纯底色的图作为词云的背景形状,可以用美图秀秀进行扣图,我就找了马的图片进行扣图,放置到白底图层中(PNG或JPG都可以),效果如下:

生成词云需要调用字体库,找到计算机本地的字库,选择合适某个字库,复制一份,放到当前目录下。路径:此电脑 > C: > Windows > Fonts

我们当前目录下的文件如下所示:

from wordcloud import WordCloud
import matplotlib.pyplot as plt
import numpy
from PIL import Image

mask = numpy.array(Image.open("horseBackImg.jpg"))
# 读取词云形状背景图

with open("wordCloud_Save.txt", "r", encoding='utf-8') as f:
    txt = f.read()
    f.close()
#读取txt(所要生成词云的数据)

word = WordCloud(background_color="white", width=1200, height=1200, font_path='STXINGKA.TTF', mask=mask,).generate(txt)
word.to_file('finalImg.png')

#background_color 生成词云图的背景颜色。red,blue

print("词云图片已保存,名称为finalImg.png")

plt.imshow(word)  # 使用plt库显示图片
plt.axis("off")  # 关闭坐标轴
plt.show()   # 启动展示

完整的代码展示:

from wordcloud import WordCloud
import matplotlib.pyplot as plt
import jieba
import numpy
from PIL import Image

with open("wordCloudData.txt", "r", encoding='utf-8') as f:  # 读取我们的待处理文本
    txt = f.read()
    f.close()

remove_data = [",", "。", '\n', '\xa0', ' ']  # 无效数据

# 去除无效数据
for r_data in remove_data:
    txt = txt.replace(r_data, "")

words = jieba.lcut(txt)  # 使用精确分词模式进行分词后保存为word列表

with open("wordCloud_Save.txt", "w", encoding='utf-8') as f:
    for word in words:
        f.write(word + ' ')
    f.close()
    print("File save successfully")

# UnicodeDecodeError: 'gbk' codec can't decode byte 0xad in position 111: illegal multibyte sequence
# 如果出现上述报错:encoding='utf-8' 没加的缘故


mask = numpy.array(Image.open("horseBackImg.jpg"))
with open("wordCloud_Save.txt", "r", encoding='utf-8') as f:
    txt = f.read()
    f.close()

word = WordCloud(background_color="white", width=2000, height=2000, font_path='STXINGKA.TTF', mask=mask,).generate(txt)
word.to_file('finalImg.png')

print("词云图片已保存,名称为finalImg.png")

plt.imshow(word)  # 使用plt库显示图片
plt.axis("off")
plt.show()

点击运行,展示效果

17、Falsk服务器搭建

Python能够进行Web服务器端的开发,相比较而言,博主Java栈进行web开发也学了很久,对于Java方向流行的Spring框架,分布式模式也有所学习。那么Python也可以进行Web的开发,有轻量级的Flask框架,也有流行的重量级Django框架。

重量级的框架:为方便业务程序的开发,提供了丰富的工具、组件,如D
jango
轻量级的框架:只提供Web框架的核心功能,自由、灵活、高度定制,如Flask、Tornado

我们先介绍Falsk框架,Flask是一个轻量级的基于Python的web框架。

首先我们要先安装第三方库:falsk

from flask import Flask

app = Flask("MyWebServer") 
# MyWebServer是我们的app名称,可以自起

@app.route('/index')  
# ‘/index’ 服务器的映射目录,相对Spring框架中的mapping
def Hello_World():
# 对于映射目录绑定的服务器端代码
    return '唤醒手腕你是真的帅呀!'

if __name__ == '__main__':
    app.run()
# 以上这两行代码,是运行这个python文件的意思,就是启动Start项目的意思。

Falsk默认开启的是5000端口,而在Springboot中默认开启的是:8080端口,两者是有区别的。

我们打开我们的浏览器进行访问:

假如我们想更改我们的端口号,可以在app.run()中加入相应的参数

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=8080, debug=True)
    
# host 主机地址,port端口号,
# debug是否开启debug模式(修改代码自动重启服务器,后端报错,返回真实报错信息给前端)

测试POST请求方式

我们先制作一个表单提交的页面,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="http://127.0.0.1:5000/form" method="post">
        <input type="text" name="username" placeholder="Please input your username:">
        <input type="password" name="password"  placeholder="Please input your password">
        <input type="submit" value="Submit">
    </form>
</body>
</html>

服务器端的Python代码如下所示:

from flask import Flask, request

app = Flask("MyWebServer")


@app.route('/index')
def Login():
    with open('index.html', 'r', encoding='utf-8') as f:
        html = f.read()
    return html


@app.route('/form', methods=["POST"])
def ShowData():
    print(request.form['username'])
    print(request.form['password'])
    return "username : " + request.form['username'] + " and password : " + request.form['password']


if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5000, debug=True)

我们打开浏览器进行访问:http://127.0.0.1:5000/index,填写表单数据然后提交,结果如下所示:

动态路由,路径参数的实现方式:

@app.route('/user/<username>/friends')
def user_friends(username):
    print(username)
    return 'hello ' + username

如何进行返回JSON类型的数据

第一种方式:

@app.route('/json', methods=["GET"])
def GetJsonData():
    print("返回json数据")
    print()
    return json.dumps({"name": "wristwaking", "time": str(time.localtime())})

第二种方式:
from flask import jsonify
def GetJsonData():
    print("返回json数据")
    print()
    return jsonify({"name": "wristwaking", "time": str(time.localtime())})

那么 jsonify 、json.dumps 两种方式有何区别呢?

return jsonify 不仅会把对象进行转换成json格式字符串,还会设置响应头Content-Type:application/json,而 json.dumps 仅仅只是把对象进行转换成json格式字符串。

如何绑定静态资源目录,和模板文件文件目录

app = Flask(__name__, static_url_path='/app', static_folder='static_files', template_folder='template_files')

# static_url_path 静态资源访问路径,默认:/ + static
# static_folder 静态文件夹目录,默认:static 例如:媒体文件 资源文件
# template_folder 模板文件目录,默认:template 例如:html文件

Flask将配置信息保存到了app.config属性中,该属性可以按照字典类型进行操作。

从配置对象中加载config信息

class DefaultConfig(object):
    database_url = 'localhost'
    database_user = 'root'
    database_password = 'root'
    
app.config.from_object(DefaultConfig)

查看配置路由的信息:

print(app.url_map)

在Flask中,使用蓝图Blueprint来分模块组织管理

Blueprint可以单独具有自己的模板、静态文件或者其它的通用操作方法,它并不是必须要实现应用的视图和函数的。

在一个应用初始化时,就应该要注册需要使用的Blueprint

但是一个Blueprint并不是一个完整的应用,它不能独立于应用运行,而必须要注册到某一个应用中。

__init__.py 文件

from flask import Blueprint
student_bp = Blueprint("student", __name__)

from . import student
student.py 文件

from . import student_bp

@student_bp.route("/student")
def showStudent():
    print("students")
server.py 文件  应用程序 (进行引入定义的蓝图)

from students import student_bp
app.register_blueprint(student_bp, url_prefix="/stu")

# url_prefix 预地址设置

运行结果如下所示:

18、Flask相关配置介绍

request相关参数的介绍:

页面的重定向或者请求的转发:

return redirect(重定向地址)

# 如何返回模板页面
# return render_template(页面的地址)

可以返回一个元组,这样的元组必须是(response, status, headers)的形式,且至少包含一个元素。status值会覆盖状态代码, headers可以是一个列表或字典,作为额外的消息标头值。

@student_bp.route("/student")
def showStudent():
    print("students")
    return "这是响应体", 520

在浏览器中请求可以看到结果如下:

如何进行cookie的设置:

首先引入flask库下的make_response

from flask import make_response

@student_bp.route("/student")
def showStudent():
    resp = make_response("helle student")
    resp.status_code = 200
    resp.set_cookie("username", "wristwaking")
    # request.cookies.get("username") 读取cookie
    # delete_cookie("username")
    return resp

在浏览器中请求可以看到结果如下:

如何进行session的设置:

@student_bp.route("/student")
def showStudent():
    resp = make_response("helle student")
    resp.status_code = 200
    resp.set_cookie("username", "wristwaking")
    session['loginer'] = '唤醒手腕'
    print(session.get('loginer'))
    return resp

在浏览器中请求可以看到结果如下:

app.secret_key = "wristwaking"

相比较而言,在Springboot框架中,我们是把session存到服务器内存中,为了对于session更好的管理,如何做到高效率的读写,我们引入Redis数据库作为中间件。Redis数据库,每秒的读可以达到11万次,效率是非常高的。

给定状态代码:abort方法

抛出一个给定状态代码的HTTP Exception或者指定响应,例如想要用一个页面未找到异常来请求,你可以调用abort(404)

from flask import abort
data = request.args.get("data")
    if data is None:
        abort(400)

    return "you wanna get data = {}".format(data)

统一异常处理:errorhandler 装饰器

@app.errorhandler()注册一个错误处理程序,当程序抛出指定错误状态码的时候,就会调用该装饰器所装饰的方法参数:

code_or_exception - HTTP的错误状态码或指定异常

例如统一处理状态码为500的错误给用户友好的提示:

@app.errorhandler(500)
def internal_server_erroi(e):
	return '服务器搬家了'

例如统一处理ZeroDivisionError的提示:

@app.errorhandler(ZeroDivisionError)
def zero_division_error():
    return "不能被0除"

关于请求钩子中间件的介绍:(相对与Spring就是Filter过滤器)

before_request

  • 在每次请求前执行
  • 如果在某修饰的函数中返回了一个响应,视图函数将不再被调用

after_request

  • 如果没有抛出错误,在每次请求后执行
  • 接受一个参数︰视图函数作出的响应
  • 在此函数中可以对响应值在返回之前做最后一步修改处理
  • 需要将参数中的响应在此参数中进行返回

teardown_request

  • 在每次请求后执行
  • 接受一个参数:错误信息,如果有相关错误抛出
# 在第一次请求之前调用,可以在此方法内部做一些初始化操作
@app.before_first_request
def before_first_request():
    print("before_first_request")


# 在每一次请求之前调用,这时候已经有请求了,可能在这个方法里面做请求的校验
# 如果请求的校验不成功,可以直接在此方法中进行响应,直接return之后那么就不会执行视图函数
@app.before_request
def before_request():
    print("before_request")


# 在执行完视图函数之后会调用,并且会把视图函数所生成的响应传入,可以在此方法中对响应做最后一步统一的处理
@app.after_request
def after_request(response):
    print("after_request")
    response.headers["Content-Type"] = "application/json"
    return response


# 请每一次请求之后都会调用,会接受一个参数,参数是服务器出现的错误信息
@app.teardown_request
def teardown_request(response):
    print("teardown_request")
    print(response)

启动服务器,第1次请求,展示的效果如下所示:

启动服务器,第2次请求,展示的效果如下所示:

请求上下文 Request context 与 应用上下文 Application context

Flask是一个基于WerkZeug实现的框架,因此Flask的App Context和Request Context是基于WerkZeug的Local Stack的实现。

这两种上下文对象类定义在flask.ctx中,ctx.push会将当前的上下文对象压栈压入flask._request_ctx_stack中,这个_request_ctx_stack同样也是个Thread Local对象,也就是在每个线程中都不一样,上下文压入栈后,再次请求的时候都是通过_request_ctx_stack.top在栈的顶端取,所取到的永远是属于自己线程的对象,这样不同线程之间的上下文就做到了隔离。请求结束后,线程退出,ThreadLocal本地变量也随即销毁,然后调用ctx.pop()弹出上下文对象并回收内存。

引用博客地址:https://zhuanlan.zhihu/p/86341642

19、LocalStack的使用

首先最近在学习flask的时候,对flask上下文的原理不是太清楚,关于localstack的实现逻辑不是很清楚,于是各种百度了解了下,这里写过博客备忘下,首先在理解localstack之前,我们要先了解下python自带的 thread.local

首先讲下Spring中的ThreadLocal?

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。

Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

	private static final ThreadLocal<Map<Object, Object>> resources =
			new NamedThreadLocal<>("Transactional resources");

	private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

	private static final ThreadLocal<String> currentTransactionName =
			new NamedThreadLocal<>("Current transaction name");

  ······ 以下省略

再比如说:很多场景下cookie,session等数据隔离都是通过ThreadLocal去做实现的。

Python中的ThreadLocal介绍

我们知道多线程环境下,每一个线程均可以使用所属进程的全局变量。如果一个线程对全局变量进行了修改,将会影响到其他所有的线程。为了避免多个线程同时对变量进行修改,引入了线程同步机制,通过互斥锁,条件变量或者读写锁来控制对全局变量的访问。

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。

有时候使用局部变量不太方便,比如函数之间互相调用的时候,参数的传递,这时候如果使用全局变量也不行,因为每个线程处理的对象不同,因此 python 还提供了 ThreadLocal 变量,它本身是一个全局变量,但是每个线程却可以利用它来保存属于自己的私有数据,这些私有数据对其他线程也是不可见的,这样就一举两得了。

下图形象的给出了这几个变量之间的关系:

从图中可以看出进程包含线程,不同的线程可以使用同一个全局变量,但全局变量的改动也会影响到其他的线程,此时有了threadlocal变量的出现,即解决了全局变量的问题,又解决了不同线程间的局部变量改变的问题,互不影响。

在线程中使用局部变量则不存在这个问题,因为每个线程的局部变量不能被其他线程访问:

# coding=utf-8
import threading


def show(num):
    print('i am %s num=%s \n' % (threading.current_thread().getName(), num))

def thread_cal():
    local_num = 0  # 局部变量
    for _ in range(5):
        # time.sleep(2)
        local_num += 1
        show(local_num)

threads = []
for i in range(5):
    threads.append(threading.Thread(target=thread_cal))
    threads[i].start()
for i in range(5):
    threads[i].join()  # 阻碍主进程

print('main process end!')

从代码的运行结果可以看出这里每个线程都有自己的 local_num,各个线程之间互不干涉。

上面程序中我们需要给 show 函数传递 local_num 局部变量,并没有什么不妥。不过考虑在实际生产环境中,我们可能会调用很多函数,每个函数都需要很多局部变量,这时候用传递参数的方法会很不友好。

为了解决这个问题,一个直观的的方法就是建立一个全局字典,保存进程 ID 到该进程局部变量的映射关系,运行中的线程可以根据自己的 ID 来获取本身拥有的数据。这样,就可以避免在函数调用中传递参数,如下示例:

# coding=utf-8
import threading

global_data = {}

def show():
    cur_thread = threading.current_thread()
    print('i am %s num=%s \n' % (cur_thread.getName(), global_data[cur_thread]))

def thread_cal():
    global global_data
    cur_thread = threading.current_thread()
    global_data[cur_thread] = 0
    for i in range(5):
        # time.sleep(2)
        global_data[cur_thread] += 1
        show()

threads = []
for i in range(5):
    threads.append(threading.Thread(target=thread_cal))
    threads[i].start()
for i in range(5):
    threads[i].join()  # 阻碍主进程

print('main process end!')

保存一个全局字典,然后将线程标识符作为key,相应线程的局部数据作为 value,这种做法略显繁琐。而且这里并没有真正做到线程之间数据的隔离,因为每个线程都可以读取到全局的字典,每个线程都可以对字典内容进行更改。

为了更好解决这个问题,python 线程库实现了 ThreadLocal 变量(很多语言都有类似的实现,比如Java)。ThreadLocal 真正做到了线程之间的数据隔离,并且使用时不需要手动获取自己的线程 ID,如下示例:

import threading
import time

global_data = threading.local()

def show():
    cur_thread = threading.current_thread()
    print('i am %s num=%s \n' % (cur_thread.getName(), global_data.num))

def thread_cal():
    global_data.num = 0
    # cur_thread=threading.current_thread()
    # global_data[cur_thread]=0
    for i in range(5):
        global_data.num += 1
        time.sleep(1)
        show()

threads = []

for i in range(5):
    threads.append(threading.Thread(target=thread_cal))
    threads[i].start()
for i in range(5):
    threads[i].join()  # 阻碍主进程

print('main thread:', global_data.__dict__)  # {}

上面示例中每个线程都可以通过 global_data.num 获得自己独有的数据,并且每个线程读取到的 global_data 都不同,真正做到线程之间的隔离。

有关threading的参数介绍

  1. threading.active_count() 返回当前存活着的Tread对象个数

  2. threading.current_thread() 返回当前正在运行的线程的Tread对象

  3. threading.enumerate() 返回一个列表,列表里面是还存活的Tread对象

  4. threading.Thread(group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None)创建线程,直接使用Tread类这是一种方法,另一种方法思新建一个类然后继承threading.Thread

    group 应该为 None;为了日后扩展 ThreadGroup 类实现而保留。

    target 是用于run()方法调用的可调用对象。默认是 None,表示不需要调用任何方法。

    name 是线程名称。默认情况下,由 “Thread-N” 格式构成一个唯一的名称,其中 N 是小的十进制数。

    args 是用于调用目标函数的参数元组。默认是 ()。

    kwargs 是用于调用目标函数的关键字参数字典。默认是 {}。 如果不是 None,daemon 参数将显式地设置该线程是否为守护模式。 如果是None (默认值),线程将继承当前线程的守护模式属性。

  5. Thread类的start()方法用来开始一个线程。

  6. hread类的join(timeout=None)方法会让开启线程的线程(一般指主线程)等待,阻塞这个线程,直到这个线程运行结束才结束等待。timeout的参数值为浮点数,用于设置操作超时的时间。

  7. threading.Lock 锁对象,可以通过它来创建锁被创建时为非锁定状态,原始锁有两种状态锁定和非锁定。

  8. Lock对象acquire(blocking=True, timeout=-1)方法,获得锁。

    当锁的状态为非锁定时, acquire() 将锁状态改为锁定并立即返回(即执行下面的程序)。

    当状态是锁定时, acquire() 将阻塞(将发起获得锁的线程挂起直到锁被释放获得锁),当其他线程调用 release() 将锁改为非锁定状态后(即锁被释放后), 被挂起线程的acquire() 将获得锁且重置其为锁定状态并返回(与1一致)。

    blocking 参数为bool值(默认True),可以阻塞或非阻塞地获得锁(即无法获得锁时是否阻塞线程)

    timeout 参数为浮点数(默认-1),当无法获得锁时,timeout为正决定阻塞的时间,为负数时为无限等待。blocking为False时timeout无作用(不阻塞当然涉及不到阻塞的时间)

  9. Lock对象release()方法,释放锁。

    当锁被锁定,将它重置为未锁定,并返回。如果其他线程正在等待这个锁解锁而被阻塞,只允许其中一个允许。

    在未锁定的锁调用时,会引发 RuntimeError 异常。

  10. Lock对象的locked()方法,用来判断是否获得了锁。

锁,一般用在两个线程同时使用一个公共变量的情况下。为了防止两个线程同时修改变量导致的混乱。

import threading,time


thread_lock = threading.Lock()  # 创建锁
share = ''


def thread_1():
    thread_lock.acquire()  # 锁定锁并返回
    global share
    for i in range(10):
        share = 'hi'
        print(share)
    thread_lock.release()


def thread_2():
    thread_lock.acquire()
    global share
    for i in range(10):
        share = 'hello'
        print(share)
    thread_lock.release()


if __name__ == '__main__':
    thread1=threading.Thread(target=thread_1)
    thread2=threading.Thread(target=thread_2)
    thread1.start()
    thread2.start()

  1. threading.Event() 创建事件对象(初始为False),这是线程之间通信的最简单机制之一:一个线程发出事件信号,而其他线程等待该信号。
  2. Event 对象的is_set()方法,当内部标志为True返回True
  3. Event 对象的set()方法,将内部标志设置为True
  4. Eevet 对象的wait(timeout=None)方法,如果内部标志为False,则线程进入阻塞直到内部标志改变为True。
  5. Event 对象的clear()方法,设置内部标志为False
import threading
import time

lock = threading.Lock()

def test():
    lock.acquire()
    for i in range(5):
        print(threading.current_thread().name + ' - ' + str(i))
        time.sleep(1)
    lock.release()

thread = threading.Thread(target=test)
thread.start()


for i in range(5):
    lock.acquire()
    print(threading.current_thread().name + ' - ' + str(i))
    time.sleep(1)
    lock.release()

打印时候存在换行与输出不具有原子性。

import threading
import time

lock = threading.Lock()
def lock_print(value):
    with lock:
        print(value)

def test():
    for i in range(5):
        lock_print(threading.current_thread().name + ' - ' + str(i))
        time.sleep(1)

thread = threading.Thread(target=test)
thread.start()


for i in range(5):
    lock_print(threading.current_thread().name + ' - ' + str(i))
    time.sleep(1)

20、asyncio异步编程

何时使用异步编程?

通常,使用异步的最佳时机是当您尝试执行具有以下特征的工作时:

  1. 工作需要很长时间才能完成。
  2. 延迟涉及等待I / O(磁盘或网络)操作,而不是计算。
  3. 该工作涉及到一次执行许多I / O操作, 或者当您还试图完成其他任务时发生一项或多项I / O操作。

异步使您可以并行设置多个任务并有效地遍历它们,而不会阻塞应用程序的其余部分。

可以很好地与异步工作的一些任务示例:

  1. 网页抓取,如上所述。
  2. 网络服务(例如,Web服务器或框架)。
  3. 协调来自多个源的结果的程序,这些结果需要很长时间才能返回值(例如,同时进行的数据库查询)。

重要的是要注意,异步编程不同于多线程或多处理。 异步操作都在同一个线程中运行,但是它们可以根据需要相互转化,这使得异步处理比线程或多处理多种任务的效率更高。

Python async await 和 asyncio

Python最近添加了两个关键字async和await ,用于创建异步操作。 考虑以下脚本:

21、redis库的使用

redis-py提供两个类Redis和StrictRedis用于实现Redis的命令,StrictRedis用于实现大部分官方的命令,并使用官方的语法和命令,Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py

如何进行远程联机宝塔的腾讯云Redis数据库?

首先在腾讯云和宝塔面板同时放行6379默认端口号,然后配置文件中把bind的IP绑定设置为0.0.0.0,另外设置Redis的密码增加Auth权限认证的防护。

Python连接redis需要下载第三方库:redis

import redis
conn = redis.Redis(host='127.0.0.1', password='你的密码', port=6379)
# 可以使用url方式连接到数据库
# conn = redis.Redis.from_url('redis://@localhost:6379/1')
conn.set('name', '唤醒手腕')
print(conn.get('name'))

运行结果如下展示:

解决方案,总结:

注意:在redis中存储的键值对均为bytes类型

如果我们希望查询某一个str类型的键是否在数据库中,需要使用encode,将str转换为bytes,看是否在db.keys()中;使用decode将查询出的bytes值转换为str

如果我们总是使用encodedecode来编码 - 解码键值对,会非常的麻烦。在python中,我们可以通过声明redis连接池的decode_responses字段来对键值对进行默认编码进行修改。

conn = redis.StrictRedis(host='', password='', port=6379, decode_responses=True)

连接池技术的实现

redis-py使用connection pool来管理对一个redis server的所有连接,避免每次建立、释放连接的开销。默认,每个Redis实例都会维护一个自己的连接池。可以直接建立一个连接池,然后作为参数Redis,这样就可以实现多个Redis实例共享一个连接池

from redis import ConnectionPool
POOL=ConnectionPool(host='127.0.0.1',port=6379,max_connections=100)

使用连接池:test_redis.py

import redis
from redis_pool import POOl
conn = redis.Redis(connection_pool=POOl)
conn.set('name', 'LinWOW')
print(conn.get('name'))
redis://[:password]@host:port/db    # TCP连接
rediss://[:password]@host:port/db   # Redis TCP+SSL 连接
unix://[:password]@/path/to/socket.sock?db=db    # Redis Unix Socket 连接

如何遍历查询数据?

conn.set('name', '唤醒手腕')
conn.set('password', '5201314')
data = conn.keys() # 列表类型
data.reverse()
for item in data:
    print(item + ' : ' + conn.get(item))

22、snowflake雪花算法

首先讲下唯一ID生成策略中的UUID:

UUID应该是大家耳熟能详的一个东西了,它的全称叫 通用唯一识别码(英語:Universally Unique Identifier,缩写:UUID)

import uuid

print(uuid.uuid1())
# 47869270-6aea-11ec-9951-18cc18368a07
print(uuid.uuid1().hex) 
# 478692716aea11eca11e18cc18368a07

通过以上代码我们用Python生成来一个UUID字符串,用的是uuid1方法生成,默认会生成一个带减号 ( - ) 的字符串,我们可以通过hex数据拿到不带减号的版本,可以根据实际情况使用。

相信使用过mongodb的朋友们很清楚,它的文档默认的key其实也是一个uuid,所以我们也可以利用mongodb的ObjectId来产生一个UUID。

在python里直接使用一个叫bson的第三方包即可,BSON是一种计算机数据交换格式,主要被用作MongoDB数据库中的数据存储和网络传输格式。

主要代码如下:

import bson
demoid = bson.ObjectId()
print(demoid)

雪花算法是解决分布式id的一个高效的方案,大部分互联网公司都在使用雪花算法,当然还有公司自己实现其他的方案。

雪花算法就是使用64位long类型的数据存储id,最高位一位存储0或者1,0代表整数,1代表负数,一般都是0,所以最高位不变,41位存储毫秒级时间戳,10位存储机器码(包括5位datacenterId和5位workerId),12存储序列号。这样最大2的10次方的机器,也就是1024台机器,最多每毫秒每台机器产生2的12次方也就是4096个id。(下面有代码实现)

但是一般我们没有那么多台机器,所以我们也可以使用53位来存储id。为什么要用53位?

因为我们几乎都是跟web页面打交道,就需要跟js打交道,js支持最大的整型范围为53位,超过这个范围就会丢失精度,53之内可以直接由js读取,超过53位就需要转换成字符串才能保证js处理正确。53存储的话,32位存储秒级时间戳,5位存储机器码,16位存储序列化,这样每台机器每秒可以生产65536个不重复的id。

import snowflake.client

# 链接服务端并初始化一个pysnowflake客户端
snowflake.client.setup()


# 生成一个全局唯一的ID(在MySQL中可以用BIGINT UNSIGNED对应)
def get_distributed_id():
    return snowflake.client.get_guid()

if __name__ == '__main__':
    get_distributed_id()

23、re正则表达式

正则表达式是用来匹配与查找字符串的,从网上爬取数据自然或多或少会用到正则表达式,python的正则表达式要先引入re模块,正则表达式以r引导,例如:

import re
re.match #从开始位置开始匹配,如果开头没有则无

re.search #搜索整个字符串

re.findall #搜索整个字符串,返回一个list

其中r“\d+”正则表达式表示匹配连续的多个数值,search是re中的函数,从"abc123cd1234"字符串中搜索连续的数值,得到"123",返回一个匹配对象,结果如上:

import re

reg = r"\d+"

result = re.search(reg, "abcd123efg1234")
print(result) # <re.Match object; span=(4, 7), match='123'>
result = re.findall(reg, "abcd123efg1234")
print(result) # ['123', '1234']

字符串"\d"匹配0~9之间的一个数值,字符"+"重复前面一个匹配字符一次或者多次。

注意:r"d\d+"第一个字符要匹配"b",后面是连续的多个数字,因此是"d123",不是"g123".

"\w"匹配包括下划线子内的单词字符,等价于"[a-zA-Z0-9]"

import re

reg = r"\w+"

result = re.search(reg, "Python is easy")
print(result)
# <re.Match object; span=(0, 6), match='Python'>

[a-zA-Z]|[0-9]表示满足数字或字母就可以匹配,这个规则等价于[a-zA-Z0-9]

常用的正则表达式:

验证Email地址:"^w+[-+.]w+)*@w+([-.]w+)*.w+([-.]w+)*$"

只能输入汉字:"^[u4e00-u9fa5],{0,}$"

匹配HTML标记的正则表达式:"/<(.*)>.*<!--1-->|<(.*) />/"

24、WebSocket通信

网站上的即时通讯是很常见的,比如网页的QQ,聊天系统等。按照以往的技术能力通常是采用轮询、Comet技术解决。

HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。

这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。

缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上

WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。

在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。

连接过程 —— 握手过程

  1. 浏览器、服务器建立TCP连接,三次握手。这是通信的基础,传输控制层,若失败后续都不执行。
  2. TCP连接成功后,浏览器通过HTTP协议向服务器传送WebSocket支持的版本号等信息。(开始前的HTTP握手)
  3. 服务器收到客户端的握手请求后,同样采用HTTP协议回馈数据。
  4. 当收到了连接成功的消息后,通过TCP通道进行传输通信。


WebSocket与HTTP的关系

相同点:

  1. 都是一样基于TCP的,都是可靠性传输协议。
  2. 都是应用层协议。

不同点:

  1. WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
  2. WebSocket是需要握手进行建立连接的。

联系:

WebSocket在建立握手时,数据是通过HTTP传输的。但是建立之后,在真正传输时候是不需要HTTP协议的。

http状态码101概述

切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议,例如,切换到HTTP的新版本协议。


后端代码实现如下所示:

from flask import request, Flask
from geventwebsocket.handler import WebSocketHandler  # 提供WS协议处理
from geventwebsocket.server import WSGIServer  # 承载服务
from geventwebsocket.websocket import WebSocket  # 语法提示

app = Flask(__name__)

user_socket_list = []


@app.route("/socket")
def chat_socket():
    # 获取当前客户端与服务器的Socket连接
    user_socket = request.environ.get("wsgi.websocket")  # type:WebSocket
    if user_socket:
        user_socket_list.append(user_socket)
        print(len(user_socket_list), user_socket_list)
    # 1 [<geventwebsocket.websocket.WebSocket object at 0x000001D0D70E1458>]
    print(user_socket, "OK 连接已经建立好了,接下来发消息吧")
    while 1:
        # 等待前端将消息发送过来
        msg = user_socket.receive()
        print(msg)

        for u_socket in user_socket_list:
            try:
                u_socket.send(msg)
            except:
                continue


@app.route("/chat")
def chat():
    with open("index.html", 'r', encoding='utf-8') as f:
        html = f.read()
        f.close()
    return html


if __name__ == '__main__':
    http_serve = WSGIServer(("0.0.0.0", 5000), app, handler_class=WebSocketHandler)
    # 这种启动方式和app.run()不冲突,该启动方式发什么请求都可以接受到
    http_serve.serve_forever()

25、Flask文件上传

Content-Type:multipart/form-data :请求体二进制数据,并以边界boundary来分割field,每一个可以设置下一级的数据类型Content-Type;

前端代码展示如下:

<form action="/load" method="post" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit" value="upload"/>
</form>

后端代码展示如下:

@app.route("/load", methods=['POST'])
def load():
    file = request.files['file']
    print(file) # <FileStorage: 'picture.jpg' ('image/jpeg')>
    print(file.filename) # picture.jpg
    print(file.content_type) # image/jpeg
    print(file.mimetype) # image/jpeg
    return "上传成功!"

当web服务器收到静态的资源文件请求时,依据请求文件的后缀名在服务器的MIME配置文件中找到对应的MIME-Type,再根据MIME-Type设置HTTP Response的Content-Type,然后浏览器根据Content-Type的值处理文件。

在Flask服务框架中,当客户端向服务端传送文件的时候,文件到服务端的时候,会被以stream的方式作为临时文件缓存在内存中。

直接保存在服务端(当你知道上传的究竟是pdf还是png还是jpg…时)

filestorage.save('临时保存.pdf')

读取成二进制流使用

file_bytes = filestorage.read()

此时:通过.read()方法后,FileStorage对象已经从缓存中被拿出来使用了,所以很多人在后面再次用FileStorage.read()的方式的时候,没有读到任何数据(空的bytes数据),所以,最好的办法就是,用个变量保存它,如上面的file_bytes,后面需要使用它的时候,再也不用担心读不到数据了。

综合案例:

前端上传图片,并且预览的代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>图片上传预览</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
    <script src="https://code.jquery/jquery-3.3.1.min.js"></script>
</head>
<body>
    <!--设置input的type和accept,当然也可设置multiple允许多文件上传,这里不做设置-->
    <form method="post" action="/load" enctype="multipart/form-data">
        <input type="file" accept="image/*" name="file" onchange="showImg(this)"/>
        <input type="submit" value="点击提交">
    </form>
    <h2>图片预览效果:</h2>
    <img src="" alt="" id="img">
    <script>
        function showImg(obj) {
            var file = $(obj)[0].files[0];                      // 获取文件信息
            var imgdata = '';
            if (file) {
                var reader = new FileReader();                  //调用FileReader
                reader.readAsDataURL(file);                     // 将文件读取为 DataURL(base64)
                reader.onload = function (event) {              // 读取操作完成时触发。
                    $("#img").attr('src', event.target.result)
                    // 将img标签的src绑定为DataURL
                };
            } else { alert("上传失败"); }
        }
    </script>
    <style> img { width: calc(100% - 50px);display: block;margin: 0 auto } </style>
</body>
</html>

后端Flask返回图片(字节流返回)

from flask import Flask, make_response, request
from PIL import Image

@app.route("/load", methods=['POST'])
def load():
    save_path = "D:/我的图片/picture.jpg"
    file = request.files["file"]
    file.save(save_path)
    img = Image.open(save_path)
    img = img.convert('L')
    img.save(save_path)
    with open(save_path, 'rb') as file_bytes:
        image = file_bytes.read()
    resp = make_response(image)
    resp.status_code = 200
    resp.headers["Content-Type"] = "image/jpg"
    return resp

效果展示如下:上传图片转变为灰度图案例

26、Centos安装pip

如果使用yum安装第三方包,出现如下的报错:

cannot open Packages database in /var/lib/rpm

解决方案如下:

[root@VM_0_17_centos rabbitmq]# cd /var/lib/rpm
[root@VM_0_17_centos rpm]# ls
Basenames  Conflictname  __db.001  __db.002  __db.003  Dirnames  Group  Installtid  Name  Obsoletename  Packages  Providename  Requirename  Sha1header  Sigmd5  Triggername
[root@VM_0_17_centos rpm]# rm -rf __db*
[root@VM_0_17_centos rpm]# rpm --rebuilddb

yum安装pip的方法:

  1. 没有python-pip包就执行命令 yum -y install epel-release

  2. 执行成功之后,再次执行 yum install python-pip

  3. 对安装好的pip进行升级 pip install --upgrade pip

有关pip的常见操作

pip(3) -V:查看本地安装的pip版本号

pip(3) list:查看本地已经安装的python库

pip(3) install 包名:安装某第三方的库

pip(3) uninstall 包名:卸载本地已经安装某第三方的库

有关于Flask开启外网访问的权限

指定使用一个 host,默认是 localhost,如果你希望服务器外部可访问,制定:host: "0.0.0.0"

不过我这样设置后,同样能够在本地通过 localhost 访问到,不禁想要掰一掰两者的区别,在这里做个总结:

每个主机都可能有多个 ip 地址,比如多个网卡或多 ip,127.0.0.1 是本地环回地址,专供自己访问自己,速度快(不用经过整个协议栈),永远都不能出现在主机外部的网络中,所以只适合用在开发环境。

localhost 只是 127.0.0.1 的别名

0.0.0.0 有几个不同的含义,不过当告诉服务器监听了 0.0.0.0,意味着监听每一个可用的网络接口,从服务器进程的角度来看,IP 地址为 127.0.0.1 的环回适配器看起来就像机器上的任何其他网络适配器一样,因此被告知监听 0.0.0.0 的服务器也将接受该接口上的连接。

因此在实际应用中,一般我们在服务端绑定端口的时候可以选择绑定到 0.0.0.0,这样我的服务访问就可以通过主机的多个 ip 地址访问我的服务。

比如我有一台服务器,一个外网地址 A,一个内网地址 B,如果我绑定的端口指定了 0.0.0.0,那么通过内网地址或外网地址都可以访问应用。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, World!'


if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

Linux运行python的介绍:

ps -ef|grep python # 查看当前python的进程

nohup python server.py & # 保护进程的模式运行python,后台运行

pyenv: python2.7: command not found The `python2.7’ command exists in these Python versions: 2.7.5

如下面,要设置Python3.6为默认版本,同时也要可以运行其他版本如3.5,3.7,使用:

pyenv install 3.6.6
pyenv install 3.5.6
pyenv install 3.7.0
pyenv local 3.6.6 3.5.6 3.7.0

如果要恢复系统自带的Python,使用

pyenv local system

强制删除已安装程序及其关联 (python)

rpm -qa|grep python|xargs rpm -ev --allmatches --nodeps

强制删除所有残余文件 (python)

whereis python |xargs rm -frv

27、configparser库使用

程序没有任何配置文件,那么它对外是全封闭的,一旦程序需要修改一些参数必须要修改程序代码本身并重新编译,为了让程序出厂后还能根据需要进行必要的配置,所以要用配置文件;配置文件有很多种,如INI配置文件,XML配置文件,cfg配置文件,还有就是可以使用系统注册表等。

本文主要介绍INI文件的格式信息。

INI ”就是英文 “initialization”的头三个字母的缩写;当然INI file的后缀名也不一定是".ini"也可以是".cfg",“.conf ”或者是”.txt"

INI文件的格式很简单,最基本的三个要素是:parameters,sections和comments

什么是parameters?

INI所包含的最基本的“元素”就是parameter;每一个parameter都有一个name和一个value,如下所示:

name = value

什么是sections ?

所有的parameters都是以sections为单位结合在一起的。所有的section名称都是独占一行,并且sections名字都被方括号包围着([ and ])

在section声明后的所有parameters都是属于该section。对于一个section没有明显的结束标志符,一个section的开始就是上一个section的结束,或者是end of the file。

Sections一般情况下不能被nested,当然特殊情况下也可以实现sections的嵌套。

section如下所示:

[section]

什么是comments ?

在INI文件中注释语句是以分号“;”开始的。所有的注释语句不管多长都是独占一行直到结束的。在分号和行结束符之间的所有内容都是被忽略的。

注释实例如下:默认都是字符类型,不需要加""

;comments text

举例ini文件的格式:

[infor]
username=唤醒手腕
password=12345
;this is my information

利用python进行读写ini文件:利用configparser库进行读写

import configparser

config = configparser.ConfigParser()
config.read_file(open('config.ini', encoding='utf-8', mode='rt'))
test_value = config.get("mysql", "username")
print(test_value)

关于更多的操作参考这篇博客:python ini 文件读写 方法

28、docx库操作word

关于为什么要用 Python 来操作 Word ?理由如下:

在我们的工作中,如果仅仅是单纯用 Word 来完成工作的文档,那必然是无可厚非,但总是有一些场景,会让你苦恼。比如大批量的从网页复制一些信息,整理到 Word 中。

开始之前,先要安装第三方库 python-docx

pip install python-docx

增加标题段 add_heading(self, text="", level=1)

生成在word文档中的效果展示如下所示:

添加空白页(分页符操作)docx.add_page_break()

添加新的自然段 add_paragraph(self, text='', style=None)

from docx import Document
from docx.document import Document as Document_Me

# create Document() object
docx: Document_Me = Document()
# docx.add_heading("第一个自然段", level=0)
docx.add_paragraph(text="第一个子自然段", style=None)
docx.save("word.docx")

有同学遇到代码不展示提示的功能,那么是因为docx对象,编辑器误以为是Document()的直接对象了,from docx.document import Document as Document_Me,去让docx对象是从属于docx.document.Document类的对象。

add_paragraph(self, text='', style=None)函数调用后会返回Paragraph类的对象

然后我们操作Paragraph类的对象中方法进行段落内容的编写。

parag = docx.add_paragraph(text="第一个子自然段", style=None)
parag.add_run("hello world")

生成在word文档中的效果展示如下所示:

生成的中文文字奇奇怪怪的,因为 docx 库对中文支持的不是很友好,所以,需要在程序里在设定下字体。初始化文档时,设置成全局即可。

把helloworld改变颜色操作:

from docx.shared import RGBColor
parag.add_run("hello world").font.color.rgb = RGBColor(255, 0, 0)

其实这些段落中文字的操作都是Run类对象的操作:

相关段落中文字的字体大小、字体粗细、字体斜体的调整如下:

案例:利用Python调整Word文档样式:原博客地址

from docx import Document
from docx.shared import Pt,RGBColor 
from docx.oxml.ns import qn
 
 
doc = Document(r"G:\huanxingshouwan\_test2.docx")
for paragraph in doc.paragraphs:
    for run in paragraph.runs:
        run.font.bold = True
        run.font.italic = True
        run.font.underline = True
        run.font.strike = True
        run.font.shadow = True
        run.font.size = Pt(18)
        run.font.color.rgb = RGBColor(255,255,0)
        run.font.name = "宋体"
        # 设置像宋体这样的中文字体,必须添加下面2行代码
        r = run._element.rPr.rFonts
        r.set(qn("w:eastAsia"),"宋体")
doc.save(r"G:\huanxingshouwan\_test1.docx")

其他细致的操作介绍:

添加表格

from docx import Document

doc = Document()

list_cont = [
    ["唤醒手腕", "男", "湖北省"],
    ["唤醒手腕", "男", "北京市"],
    ["唤醒手腕", "男", "广东省"],
    ["唤醒手腕", "男", "湖南省"]
]

table = doc.add_table(rows=4, cols=3)
for row in range(4):
    cells = table.rows[row].cells
    for col in range(3):
        cells[col].text = str(list_cont[row][col])

doc.save("test.docx")

修改段落样式:对齐样式

from docx import Document
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT

doc = Document()
print(doc.paragraphs[0].text)
doc.paragraphs[0].alignment = WD_PARAGRAPH_ALIGNMENT.CENTER

doc.save(r"对齐样式.docx")

修改段落样式:行间距

from docx import Document

doc = Document()
for paragraph in doc.paragraphs:
    paragraph.paragraph_format.line_spacing = 5.0
doc.save("行间距.docx")

29、Scrapy爬虫框架介绍

Scrapy是一个用于从网站中抓取数据的库。它是用Python编写的,可以用于抓取网页并从中提取数据。使用Scrapy可以轻松地爬取大量数据,并将这些数据用于各种用途,如数据分析、竞争情报、价格监测等。

Scrapy 框架特点

1. 灵活性:Scrapy可以用于抓取各种类型的网站,并且可以通过编写自定义的解析规则来提取所需的数据。
2. 异步性:Scrapy使用异步网络库Twisted,可以同时处理多个请求和响应,提高了抓取效率。
3. 可扩展性:Scrapy是开源的,可以轻松地扩展和定制,以适应特定的需求。
4. 容错性:Scrapy具有内置的错误处理机制,可以自动重试失败的请求,处理被屏蔽的网站,以及处理其他常见的网络问题。

Scrapy 官网介绍

Scrapy 官网:https://scrapy/

本文标签: 爬虫 手把手 第三方 基础教程 网络编程