From 3b62641f21b7dc2d081ee69b57462c0c4a7b1b49 Mon Sep 17 00:00:00 2001 From: TRHX Date: Sat, 14 Mar 2020 16:02:41 +0800 Subject: [PATCH] Site updated: 2020-03-14 16:01:34 --- 2018/08/15/A02-hexo-blog/index.html | 46 +- 2018/08/25/A03-markdown/index.html | 8 +- .../index.html | 58 +- 2018/08/29/A05-markdown-editor/index.html | 40 +- 2018/09/09/A06-install-ubuntu18.04/index.html | 32 +- 2019/02/10/A18-free-cdn/index.html | 12 +- 2019/04/14/A20-install-deepin15.9/index.html | 12 +- .../14/A22-eclipse-connects-to-sql/index.html | 30 +- 2019/07/31/A26-hexo-add-https/index.html | 14 +- 2019/08/01/A27-image-hosting/index.html | 10 +- 2019/08/23/A23-beian/index.html | 34 +- 2019/08/23/A25-SB/index.html | 6 +- 2019/08/23/A30-Python3-spider-C02/index.html | 39 +- 2019/09/03/A40-Python3-spider-C10/index.html | 8 +- 2019/09/14/A46-Python3-spider-C16/index.html | 4 +- .../index.html | 18 +- .../index.html | 4 +- archives/2019/09/index.html | 4 +- archives/2019/page/2/index.html | 4 +- archives/index.html | 2 +- archives/page/2/index.html | 2 +- archives/page/3/index.html | 2 +- archives/page/4/index.html | 2 +- archives/page/5/index.html | 2 +- archives/page/6/index.html | 2 +- archives/page/7/index.html | 2 +- atom.xml | 4 +- baidusitemap.xml | 90 +- categories/Hexo/index.html | 4 +- content.json | 2 +- friends/index.html | 30 +- index.html | 12 +- page/2/index.html | 4 +- search.xml | 2502 +++++------------ sitemap.xml | 194 +- tags/Coding-Pages/index.html | 4 +- tags/Github-Pages/index.html | 4 +- tags/Hexo/index.html | 4 +- 38 files changed, 1152 insertions(+), 2099 deletions(-) diff --git a/2018/08/15/A02-hexo-blog/index.html b/2018/08/15/A02-hexo-blog/index.html index bf3ad10a0..73a98f424 100644 --- a/2018/08/15/A02-hexo-blog/index.html +++ b/2018/08/15/A02-hexo-blog/index.html @@ -569,34 +569,34 @@

Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

– 安装 Node.js

点击此处访问官网,按需下载相应版本,默认安装可以了

-

02

+

02

注:本人在安装过程中出现了Warning 1909,无法创建快捷方式,这种情况很少出现,如果在安装过程中也有这种情况请参考百度文库(win10系统实测可行):《Win7安装程序警告1909无法创建快捷方式》

-

03

+

03

– 安装 Git

点击此处访问官网,按需下载相应版本,默认安装即可
参考资料:《如何在windows下安装GIT》  (By 俊雨廷休)
     《Pro Git(中文版)》

– 检验软件是否安装成功

同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI HereGit Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行

1
2
3
$ git --version
$ node -v
$ npm -v

-

04

-

05

+

04

+

05

– Hexo 安装

选择一个磁盘,新建一个文件夹,自己重命名文件夹(如:我的文件夹为:E\TRHX_Blog),博客相关文件将储存在此文件夹下,在该文件夹下右键鼠标,点击 Git Bash Here,输入以下 npm 命令即可安装,第一个命令表示安装 hexo,第二个命令表示安装 hexo 部署到 git page 的 deployer,如图所示即为安装成功

1
2
$ npm install hexo-cli -g
$ npm install hexo-deployer-git --save

06

– Hexo 初始化配置

在刚才新建的文件夹里面再次新建一个 Hexo 文件夹(如:我的文件夹为:E\TRHX_Blog\Hexo),进入该 Hexo 文件夹右键鼠标,点击 Git Bash Here,输入以下命令,如图所示则安装成功

1
$ hexo init

07

-

Hexo 安装完成后,将会在指定文件夹中新建所需要的文件,Hexo 文件夹下的目录如下:
08

+

Hexo 安装完成后,将会在指定文件夹中新建所需要的文件,Hexo 文件夹下的目录如下:
08

– 本地查看效果

执行以下命令,执行完即可登录 http://localhost:4000/ 查看效果

1
2
$ hexo generate
$ hexo server

显示以下信息说明操作成功:

1
INFO Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop.

-

登录 http://localhost:4000/ 查看效果:
09

+

登录 http://localhost:4000/ 查看效果:
09

– 将博客部署到 Github Pages 上

到目前为止,我们的本地博客就成功搭建了,但是现在我们只能通过本地连接查看博客,我们要做的是让其他人也能够访问我们的博客,这就需要我们将博客部署到Github Pages上

一、注册 Github 账户:点击此处访问 Github 官网,点击 Sign Up 注册账户

-

二、创建项目代码库:点击 New repository 开始创建,步骤及注意事项见图:
10

+

二、创建项目代码库:点击 New repository 开始创建,步骤及注意事项见图:
10

三、配置 SSH 密钥:只有配置好 SSH 密钥后,我们才可以通过 git 操作实现本地代码库与 Github 代码库同步,在你第一次新建的文件夹里面(如:我的文件夹为:E\TRHX_BlogGit Bash Here 输入以下命令:

1
2
$ ssh-keygen -t rsa -C "your email@example.com"
//引号里面填写你的邮箱地址,比如我的是tanrenhou@126.com

之后会出现:

1
2
3
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/you/.ssh/id_rsa):
//到这里可以直接回车将密钥按默认文件进行存储

然后会出现:

1
2
3
Enter passphrase (empty for no passphrase):
//这里是要你输入密码,其实不需要输什么密码,直接回车就行
Enter same passphrase again:

接下来屏幕会显示:

1
2
3
4
5
6
Your identification has been saved in /c/Users/you/.ssh/id_rsa.
Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
这里是各种字母数字组成的字符串,结尾是你的邮箱
The key's randomart image is:
这里也是各种字母数字符号组成的字符串

运行以下命令,将公钥的内容复制到系统粘贴板上

1
$ clip < ~/.ssh/id_rsa.pub

四、在 GitHub 账户中添加你的公钥

-

1.登陆 GitHub,进入 Settings
11

-

2.点击 SSH and GPG Keys
12

-

3.选择 New SSH key
13

-

4.粘贴密钥:
14

+

1.登陆 GitHub,进入 Settings
11

+

2.点击 SSH and GPG Keys
12

+

3.选择 New SSH key
13

+

4.粘贴密钥:
14

五、测试

输入以下命令:注意:git@github.com不要做任何更改!

1
$ ssh -T git@github.com

之后会显示:
15

@@ -604,10 +604,10 @@

六、配置 Git 个人信息

Git 会根据用户的名字和邮箱来记录提交,GitHub 也是用这些信息来做权限的处理,输入以下命令进行个人信息的设置,把名称和邮箱替换成你自己的,名字可以不是 GitHub 的昵称,但为了方便记忆,建议与 GitHub 一致

1
2
$ git config --global user.name "此处填你的用户名"
$ git config --global user.email "此处填你的邮箱"

到此为止 SSH Key 配置成功,本机已成功连接到 Github

-

– 将本地的 Hexo 文件更新到 Github 的库中

一、登录 Github 打开自己的项目 yourname.github.io
17

-

二、鼠标移到 Clone or download 按钮,选择 Use SSH
18

-

三、一键复制地址
19

-

四、打开你创建的 Hexo 文件夹(如:E:\TRHX_Blog\Hexo),右键用记事本(或者Notepad++、Vs Code等)打开该文件夹下的 _config.yml 文件
20

+

– 将本地的 Hexo 文件更新到 Github 的库中

一、登录 Github 打开自己的项目 yourname.github.io
17

+

二、鼠标移到 Clone or download 按钮,选择 Use SSH
18

+

三、一键复制地址
19

+

四、打开你创建的 Hexo 文件夹(如:E:\TRHX_Blog\Hexo),右键用记事本(或者Notepad++、Vs Code等)打开该文件夹下的 _config.yml 文件
20

五、按下图修改 _config.yml 文件并保存
21

六、在 Hexo 文件夹下分别执行以下命令

1
2
$ hexo g
$ hexo d

或者直接执行

1
$ hexo g -d

@@ -623,19 +623,19 @@ md 全称 Markdown, Markdown 是 2004 年由 John Gruberis 设计和开发的纯文本格式的语法,非常的简单实用,常用的标记符号屈指可数,几分钟即可学会, .md 文件可以使用支持 Markdown 语法的编辑器编辑,然后将写好的文章(.md文件)保存到 \Hexo\source\_posts 文件夹下即可

推荐 Windows 上使用 MarkdownPad2 或者 小书匠 编辑器,macOS 上使用 Mou 编辑器,Linux 上使用 Remarkable 编辑器,Web 端上使用 简书 ,另外可以参考我的另一篇文章:《主流 Markdown 编辑器推荐》
当我们用编辑器写好文章后,可以使用以下命令将其推送到服务器上
1
2
$ hexo g
$ hexo d


或者将两个命令合二为一输入以下命令:
1
$ hexo d -g


现在访问你的博客就可以看见写好的文章啦!
参考资料:《10款流行的Markdown编辑器》 (By xiaoxiao_engineer)
     《献给写作者的 Markdown 新手指南》 (By 简书)
     《认识与入门 Markdown》 (By Te_Lee)
     《markdown简明语法》 (By 不如)
     《markdown基本语法》 (By 高鸿祥)
     《Markdown 公式指导手册》 (By Harries)


# – 如何为博客更换自己喜欢的主题

博客也搭建好了,文章也会写了,但是!!!默认的主题并不喜欢怎么办?现在,我们就来为自己的博客更换自己喜欢的主题

-

点击此处进入 Hexo 官网的主题专栏,我们可以看见有许多的主题供我们选择
22

-

我们要做的就是把主题克隆过来,在此我们以主题 Aero-Dual 为例,点进去我们就可以看见该主题作者的博客,鼠标滑到底,我们可以看见 Theme By Levblanc 的字样(其他主题类似),点击作者 Levblanc ,页面就会跳转到该主题所有的相关文件在 Github 上的地址,复制该地址
23
24
25

+

点击此处进入 Hexo 官网的主题专栏,我们可以看见有许多的主题供我们选择
22

+

我们要做的就是把主题克隆过来,在此我们以主题 Aero-Dual 为例,点进去我们就可以看见该主题作者的博客,鼠标滑到底,我们可以看见 Theme By Levblanc 的字样(其他主题类似),点击作者 Levblanc ,页面就会跳转到该主题所有的相关文件在 Github 上的地址,复制该地址
23
24
25

再打开 Hexo 文件夹下的 themes 目录(如:E:\TRHX_Blog\Hexo\themes),右键 Git Bash Here,输入以下命令:

1
$ git clone 此处填写你刚才复制的主题地址

比如要安装 Aero-Dual 主题,则输入命令:

1
$ git clone https://github.com/levblanc/hexo-theme-aero-dual

-

等待下载完成后即可在 themes 目录下生成 hexo-theme-aero-dual 文件夹,然后打开 Hexo 文件夹下的配置文件 _config.yml ,找到关键字 theme,修改参数为:theme:hexo-theme-aero-dual (其他主题修改成相应名称即可),再次注意冒号后面有一个空格!
26

+

等待下载完成后即可在 themes 目录下生成 hexo-theme-aero-dual 文件夹,然后打开 Hexo 文件夹下的配置文件 _config.yml ,找到关键字 theme,修改参数为:theme:hexo-theme-aero-dual (其他主题修改成相应名称即可),再次注意冒号后面有一个空格!
26

返回 Hexo 目录,右键 Git Bash Here ,输入以下命令开始部署主题:

1
2
$ hexo g   
$ hexo s

此时打开浏览器,访问 http://localhost:4000/ 就可看见我们的主题已经更换了,如果感觉效果满意,我们就可以把它部署到Github上了

打开 Hexo 文件夹,右键 Git Bash Here ,输入以下命令:

1
2
3
$ hexo clean  
//该命令的作用是清除缓存,若不输入此命令,服务器有可能更新不了主题
$ hexo g -d

此时访问自己的博客即可看见更换后的主题,但我们仍然需要对主题的相关配置进行修改,比如网站标题,图标等等,Hexo 中有两份主要的配置文件,名称都是 _config.yml ,它们均是用于站点配置使用的。其中,一份位于站点根目录下(比如我的:E:\TRHX_Blog\Hexo\_config.yml),主要包含 Hexo 本身整站的配置;另一份位于主题目录下(比如我的:E:\TRHX_Blog\Hexo\themes\hexo-theme-aero-dual\_config.yml),这份配置由主题作者提供,主要用于配置主题相关的选项,一般 _config.yml 文件里都有相关注释,按需修改即可

参考资料:《有哪些好看的 Hexo 主题?》 (知乎)
     《Hexo | 配置》 (Hexo官方文档)
     《hexo常用命令笔记》 (By 小弟调调)

– 为你的 Hexo 博客配置个性域名

本人在配置域名的时候问题百出,百度的各种方法都不管用,打开网站总是 404,可能是我太笨了  o(╥﹏╥)o ,不过好在后来终于解决了这个问题

-

首先我们要购买域名,阿里云腾讯云都可以,也不贵,一年几十块钱,最便宜几块钱也能买到,以阿里云为例,我购买的域名是 itrhx.com,购买过程就不赘述了,选择阿里云的解析平台,来到阿里云的管理控制台,点击进入域名解析列表或者直接点击域名后面的解析
27

-

方法一:点击添加记录,需要添加两个记录,两个记录类型都是 CNAME ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都是填你自己的博客地址(比如我的是:trhx.github.io),保存之后域名解析就完成了!
28
方法二:两个记录类型为 A ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都为博客的 IP 地址,IP 地址可以 cmd 中输入 ping 你的博客地址 获得(比如我的:ping trhx.github.io),保存之后域名解析就完成了!

有关解析记录类型的区别可以参考《域名解析中A记录、CNAME、MX记录、NS记录的区别和联系》

+

首先我们要购买域名,阿里云腾讯云都可以,也不贵,一年几十块钱,最便宜几块钱也能买到,以阿里云为例,我购买的域名是 itrhx.com,购买过程就不赘述了,选择阿里云的解析平台,来到阿里云的管理控制台,点击进入域名解析列表或者直接点击域名后面的解析
27

+

方法一:点击添加记录,需要添加两个记录,两个记录类型都是 CNAME ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都是填你自己的博客地址(比如我的是:trhx.github.io),保存之后域名解析就完成了!
28
方法二:两个记录类型为 A ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都为博客的 IP 地址,IP 地址可以 cmd 中输入 ping 你的博客地址 获得(比如我的:ping trhx.github.io),保存之后域名解析就完成了!

有关解析记录类型的区别可以参考《域名解析中A记录、CNAME、MX记录、NS记录的区别和联系》

为了使 GitHub 接收我们的域名,还需要在博客的根目录下添加一个名为 CNAME 的文件(注意不要加.txt,没有任何后缀名!),这个文件放到 Hexo 文件夹的 source 里面,(比如我的是:E:\TRHX_Blog\Hexo\source),文件里面填写你的域名(加不加www都行),比如要填写我的域名,文件里面就写:www.itrhx.com 或者 itrhx.com,经过以上操作,别人就可以通过 www.itrhx.comitrhx.comtrhx.github.io 三个当中任意一个访问我的博客了!你的也一样!

有关加不加www的问题有以下区别:

@@ -644,7 +644,7 @@

如果你填写的是带www的,比如 www.itrhx.com ,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 http://www.itrhx.com

-

30

+

30

如果你在其他平台购买域名,或者选择 DNSPod 等其他域名解析,操作方法大同小异,遇到问题可自行百度解决!

参考资料:《推荐几家域名注册服务商》 (By Jelly Bool)
     《盘点十大免费DNS域名解析服务:稳定、可靠》

– 结语

一顿操作下来虽然有点儿累,但看见拥有了自己的博客还是非常有成就感的,人生就是需要折腾,那么现在就开始你的创作之旅吧!文章的不断积累,你会从中受益很多的!另外,这是一篇小白写的适用于小白的博客搭建教程,比较详细,有这方面基础的可以百度有简略一点儿的教程,文中如有错误还请大佬指出改正!文中涉及参考资料如有侵权请联系我删除!

@@ -735,10 +735,10 @@
-
+ diff --git a/2018/08/25/A03-markdown/index.html b/2018/08/25/A03-markdown/index.html index d8d7ad3fe..5f10f1a7b 100644 --- a/2018/08/25/A03-markdown/index.html +++ b/2018/08/25/A03-markdown/index.html @@ -543,8 +543,8 @@

– 插入音乐

打开网页版网易云音乐,选择你准备插入的音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,选择好尺寸后,复制底下的HTML代码

然后将此HTML代码粘贴到你想要放的地方,可自行调节播放器的大小,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐,比如See You Again (中英文版) - 罗艺恒这首歌曲代码如下:

1
<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=32405683&auto=1&height=66"></iframe>
-

02
03

-

– 插入视频

高考毕业了我们为下一届的学弟学妹们录制高考加油视频,我担任后期制作,在这里就以该视频为例٩(๑❛ᴗ❛๑)۶,在腾讯视频播放页面找到分享按钮,复制该视频的通用代码(其他视频播放平台也一样),粘贴到文章中对应位置即可,可根据情况调整视频播放器的大小
04

+

02
03

+

– 插入视频

高考毕业了我们为下一届的学弟学妹们录制高考加油视频,我担任后期制作,在这里就以该视频为例٩(๑❛ᴗ❛๑)۶,在腾讯视频播放页面找到分享按钮,复制该视频的通用代码(其他视频播放平台也一样),粘贴到文章中对应位置即可,可根据情况调整视频播放器的大小
04

1
<iframe frameborder="0" width="840"  height="500" src="https://v.qq.com/txp/iframe/player.html?vid=x0643zvgtf7" allowFullScreen="true"></iframe>
@@ -637,10 +637,10 @@
-
+ diff --git a/2018/08/27/A04-Hexo-blog-topic-personalization/index.html b/2018/08/27/A04-Hexo-blog-topic-personalization/index.html index 90c267f08..8ef9540ac 100644 --- a/2018/08/27/A04-Hexo-blog-topic-personalization/index.html +++ b/2018/08/27/A04-Hexo-blog-topic-personalization/index.html @@ -545,14 +545,14 @@

【01】添加评论系统

主流的评论系统有很多,比如:网易云跟帖、多说、友言、畅言、来必力(LiveRe)、Disqus、Valine、Gitment等等,目前网易云跟帖、多说、友言都已经关闭了,还有些可能需要翻墙,比较麻烦,百度了一下,最后还是选择了来必力评论系统

进入来必力官网,注册一个账号(注册时可能需要翻墙)

-
001
+
001

注册完毕之后,登录,进入安装页面,选择 City 免费版安装,安装之后你会得到一段代码

-
002
+
002
-
003
+
003
-
004
+
004

我们打开主题文件下的 _config.yml 文件,添加如下代码:


005
@@ -560,10 +560,10 @@

\themes\hexo-theme-spfk\layout\_partial\comments 文件夹下新建一个 livere.ejs 的文件,在里面填写来必力提供的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- 来必力City版安装代码 -->
<div id="lv-container" data-id="city" data-uid="这里是你的uid">
<script type="text/javascript">
(function(d, s) {
var j, e = d.getElementsByTagName(s)[0];

if (typeof LivereTower === 'function') { return; }

j = d.createElement(s);
j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
j.async = true;

e.parentNode.insertBefore(j, e);
})(document, 'script');
</script>
<noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
</div>
<!-- City版安装代码已完成 -->

打开 \themes\hexo-theme-spfk\layout\_partial\article.ejs 文件,在适当位置添加如下红框中的代码:

-
006
+
006

完成以上操作之后,我们就可以使用来必力评论系统了

-
007
+
007

【02】添加卡通人物

我在逛别人博客的时候偶然发现右下角居然有一个萌萌的卡通人物,还能根据你鼠标位置摇头,瞬间被吸引到了,赶紧也给自己博客添加一个吧!点击此处进入该项目地址

@@ -574,7 +574,7 @@

打开站点目录下的 _config.yml 文件,添加如下代码:

1
2
3
4
5
6
7
8
9
10
11
live2d:
enable: true
scriptFrom: local
model:
use: live2d-widget-model-haruto #模型选择
display:
position: right #模型位置
width: 150 #模型宽度
height: 300 #模型高度
mobile:
show: false #是否在手机端显示

设置好过后我们就拥有了一个卡通人物

-
008
+
008

【03】自定义鼠标指针样式

\themes\material-x\source\less\_base.less 文件 body 样式里写入如下代码:

@@ -623,7 +623,7 @@

\themes\hexo-theme-spfk\layout\layout.ejs 文件末尾添加以下代码:

1
2
<!-- 页面点击小红心 -->
<script type="text/javascript" src="/js/love.js"></script>

完成以上操作后,当我们点击鼠标的时候就可以看见爱心的特效了

-
009
+
009

【05】添加鼠标点击显示字体效果

\themes\hexo-theme-spfk\source\js 下新建文件 click_show_text.js,在 click_show_text.js 文件中添加以下代码:

@@ -667,12 +667,12 @@

同样的,以 spfk 主题为例,在 \themes\hexo-theme-spfk\layout\_partial\post 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div style="margin-top:10px;">
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-keyboard-o"></i>
<span class="post-meta-item-text"> 字数统计: </span>
<span class="post-count"><%= wordcount(post.content) %></span>
</span>
</span>
&nbsp; | &nbsp;
<span class="post-time">
<span class="post-meta-item-icon">
<i class="fa fa-hourglass-half"></i>
<span class="post-meta-item-text"> 阅读时长: </span>
<span class="post-count"><%= min2read(post.content) %></span>
</span>
</span>
</div>

然后在 \themes\hexo-theme-spfk\layout\_partial\article.ejs 中适当位置添加以下代码:

-
013
+
013

最后在主题目录下的 _config.yml 添加以下配置

1
word_count: true

如果显示的位置不好,可以自行更改其位置,成功配置后的效果如下:

-
014
+
014

015
@@ -683,15 +683,15 @@

【09】添加背景音乐

打开网页版网易云音乐,选择你准备添加的背景音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,复制底下的HTML代码

-
017
+
017
-
018
+
018

然后将此代码放到你想要放的地方,比如放在博客的左侧,则打开 \themes\hexo-theme-spfk\layout\_partial\left-col.ejs 文件,将复制的HTML代码粘贴进去,再进行适当的位置设置让播放器更美观,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐

-
019
+
019

最后效果如下:

-
020
+
020

这种网易云音乐外链的方式有很多局限性,因此推荐使用aplayer,GitHub地址为:https://github.com/MoePlayer/APlayer ,参考教程:《hexo上的aplayer应用》


@@ -703,19 +703,19 @@

【11】添加百度统计

百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中

访问百度统计首页,注册一个账号后登陆,添加你的博客网站

-
022
+
022

接着点击代码获取,复制该代码

-
023
+
023

然后到目录 \Hexo\themes\hexo-theme-spfk\layout\_partial 下新建一个 baidu-analytics.ejs 文件,里面粘贴你刚刚复制的代码

-
024
+
024

修改主题文件夹下的 _config.yml 文件,将你的key(图中涂掉部分)填写进去:

-
025
+
025

所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据

-
026
+
026

另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统


@@ -724,9 +724,9 @@

其中 funny.ico 是用户切换到其他标签后你网站的图标,favicon.ico 是正常图标,然后在 \themes\material-x\layout\layout.ejs 文件中添加如下代码:

1
2
<!--浏览器搞笑标题-->
<script type="text/javascript" src="/js/FunnyTitle.js"></script>

再次部署博客后就可以看见标题搞笑的效果了:

-
027
+
027
-
028
+
028

【13】背景添加动态线条效果

\Hexo\themes\hexo-theme-spfk\layout\layout.ejs 文件中添加如下代码:

@@ -739,7 +739,7 @@
  • zIndex:表示背景的z-index属性,css属性用于控制所在层的位置,默认:-1
  • 最终实现效果:

    -
    029
    +
    029

    【14】添加人体时钟

    无意中发现了个有趣的人体时钟 HONE HONE CLOCK,作者是个日本人,点击此处访问作者博客,点击此处在作者原博客上查看动态样式,点击此处查看动态大图,如果你的博客上有合适的地方,加上一个人体时钟会很有趣的

    @@ -764,9 +764,9 @@

    至此,RSS订阅功能添加完成


    【16】添加网站雪花飘落效果

    样式一和样式二分别如下:

    -
    031样式一
    +
    031样式一
    -
    032样式二
    +
    032样式二

    实现方法:在 \Hexo\themes\hexo-theme-spfk\source\js 目录下新建一个 snow.js 文件,粘贴以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /*样式一*/
    (function($){
    $.fn.snow = function(options){
    var $flake = $('<div id="snowbox" />').css({'position': 'absolute','z-index':'9999', 'top': '-50px'}).html('&#10052;'),
    documentHeight = $(document).height(),
    documentWidth = $(document).width(),
    defaults = {
    minSize : 10,
    maxSize : 20,
    newOn : 1000,
    flakeColor : "#AFDAEF" /* 此处可以定义雪花颜色,若要白色可以改为#FFFFFF */
    },
    options = $.extend({}, defaults, options);
    var interval= setInterval( function(){
    var startPositionLeft = Math.random() * documentWidth - 100,
    startOpacity = 0.5 + Math.random(),
    sizeFlake = options.minSize + Math.random() * options.maxSize,
    endPositionTop = documentHeight - 200,
    endPositionLeft = startPositionLeft - 500 + Math.random() * 500,
    durationFall = documentHeight * 10 + Math.random() * 5000;
    $flake.clone().appendTo('body').css({
    left: startPositionLeft,
    opacity: startOpacity,
    'font-size': sizeFlake,
    color: options.flakeColor
    }).animate({
    top: endPositionTop,
    left: endPositionLeft,
    opacity: 0.2
    },durationFall,'linear',function(){
    $(this).remove()
    });
    }, options.newOn);
    };
    })(jQuery);
    $(function(){
    $.fn.snow({
    minSize: 5, /* 定义雪花最小尺寸 */
    maxSize: 50,/* 定义雪花最大尺寸 */
    newOn: 300 /* 定义密集程度,数字越小越密集 */
    });
    });
    @@ -778,12 +778,12 @@

    原文链接:《分享两种圣诞节雪花特效JS代码(网站下雪效果)》


    【17】添加 Fork me on GitHub 效果

    效果图:

    -
    033
    +
    033

    点击此处可以查看更多样式,将相应样式的代码复制到你想要放的地方就OK了,代码里的链接也要替换成你的,更多创意,比如 Follow me on CSDN ,只需要用PS改掉图片里的文字,替换掉相应链接即可


    【18】添加背景动态彩带效果

    样式一是鼠标点击后彩带自动更换样式,样式二是飘动的彩带:

    -
    034
    +
    034

    实现方法:在 \themes\material-x\layout\layout.ejs 文件的body前面添加如下代码:

    1
    2
    <!-- 样式一(鼠标点击更换样式) -->
    <script src="https://g.joyinshare.com/hc/ribbon.min.js" type="text/javascript"></script>
    @@ -796,7 +796,7 @@

    然后在主题的 layout.ejs 文件中引入即可:

    1
    2
    3
    <!-- 数字雨 -->
    <canvas id="canvas" width="1440" height="900" ></canvas>
    <script type="text/javascript" src="/js/DigitalRain.js"></script>

    最终效果:

    -
    035
    +
    035

    代码来源:http://www.lxl8800.cn/Main/Resource


    @@ -903,10 +903,10 @@
    -
    + diff --git a/2018/08/29/A05-markdown-editor/index.html b/2018/08/29/A05-markdown-editor/index.html index a017d643c..5b844c3fb 100644 --- a/2018/08/29/A05-markdown-editor/index.html +++ b/2018/08/29/A05-markdown-editor/index.html @@ -540,47 +540,47 @@

    - MarkdownPad

    目前分为 MarkdownPad2 和 MarkdownPad Pro 版本,后者收费,我们使用前者足矣,用户可以通过键盘快捷键和工具栏按钮来使用或者移除 Markdown 各种语法格式,支持自定义配色方案、字体、大小和布局 、即时HTML预览、HTML和PDF导出,被很多人称赞为 Windows 平台最好用的 Markdown 编辑器,实用性强,仅支持 Windows 系统,个人觉得在 Windows 10 系统上界面并不是很好看,有时候添加音乐什么的,资源多了,实时预览会显示资源加载失败,点击此处访问 MarkdownPad 官网

    -

    MarkdownPad 2

    +

    MarkdownPad 2


    - BookPad

    无意间在 Microsoft Store 上发现的,完美搭配 Win10 系统,界面非常简洁漂亮,2017年9月份发布,大小30.82 MB,官方网站:https://sosfos.wordpress.com/ ,收费13人民币,可免费使用7天,各种功能应有尽有,和其他编辑器不相上下,本来想着百度百度看看有没有破解版,结果全网看不见 BookPad 的影子,估计是新出来的还不为人所知吧,可以直接在 Microsoft Store 搜索下载,或者点击链接获取:https://www.microsoft.com/store/apps/9N6P5ZH2SJSX

    -

    BookPad

    +

    BookPad

    BookPad2


    - 小书匠

    分为免费版和收费版,收费版¥20/年,其实免费版的功能已经足够强大了,多种编辑模式、多种主题选择、多种编辑器实现、丰富的语法支持、第三方同步、强大的文件管理功能,让人使用一次就爱上了它,支持 Windows 和 Web,推荐使用,点击此处访问小书匠官网

    -

    小书匠
    小书匠2

    +

    小书匠
    小书匠2


    - Typora

    Typora 同样支持 Windows、OS X 和 Linux,Typora 支持即时渲染技术,这也是与其他 Markdown 编辑器最显著的区别,支持数学编辑,可与 Word 直接格式转换,在 Pandoc 的支持下进行多种文档格式转换,Typora 适合那些对码字手速和排版顺畅度有要求的人群,譬如码农、网站小编等,点击此处访问 Typora 官网

    -

    Typora

    +

    Typora


    - Visual Studio Code

    Visual Studio Code 是众所周知的神器,是微软推出一款轻量级的文本编辑工具,类似于 Sublime,它已经默认集成 Markdown 文档编辑插件,原生就支持高亮 Markdown 的语法,但想要实时预览还需要选择 Markdown: Open Preview to the Side 命令实现,相关教程请点击此处点击此处 访问 Visual Studio Code 官网

    -

    Visual Studio Code

    +

    Visual Studio Code


    - Marxico

    Marxico 中文名马克飞象,提供桌面客户端以及离线 Chrome App,支持移动端 Web,可以直接把文本存到印象笔记,点击此处访问 Marxico,点击此处访问 马克飞象

    -

    马克飞象

    +

    马克飞象


    - Sublime Text 3

    Sublime Text 3 是基于 Vim 开发的跨平台代码编辑器,收费80美元,好像可以免费试用,支持 OS X、Windows、Ubuntu 等 UNIX 及 Linux 操作系统,由于其功能的多样性而广受好评,界面简约大方,定位专业,原生支持的编程语言就多达十几种,通过第三方插件,还能实现更多语法的支持,其中就包括 Markdown ,但也有个缺点,就是不能实时预览,但是用户可以通过 Markdown Preview 的插件实现对 Markdown 的预览,具体教程请点击此处查看,点击此处访问 Sublime Text 官网

    -

    SublimeText

    +

    SublimeText


    - Mou

    Mou 是一款由国人独立开发者罗晨开发的实时预览型 Markdown 编辑器,仅支持 OS X操作系统,是目前同类应用中对汉字兼容性最好的作品,也是目前最好用的免费 Markdown 编辑器,提供语法高亮、在线预览、同步滚动、全屏模式,支持自定保存、自动匹配,允许自定义主题,支持 CSS,HTML 和 PDF 导出等功能,点击此处访问 Mou 官网

    -

    Mou

    +

    Mou


    - Atom

    Atom 是 Github 专门为程序员推出的一个跨平台文本编辑器,具有简洁和直观的图形用户界面,并有很多有趣的特点:支持CSS,HTML,JavaScript等网页编程语言,当然也支持 Markdown ,支持宏,自动完成分屏功能,集成了文件管理器,点击此处访问 Atom 官网

    -

    Atom

    +

    Atom


    - Smark

    国人编写的开源软件,Windows / Linux 等主流系统跨平台支持,完美支持 LaTex 数学公式、脚注、尾注等,支持使用本地 MathJax 调用,不需要在线访问 MathJax CDN,用户可配置的 Markdown 语法高亮显示,美观整洁,多种格式文件导出支持,简洁友好的界面布局,完备的各类快捷键,能极大地提高工作效率,点击此处访问 Smark 官网

    -

    Smark

    +

    Smark


    - Haroopad

    Haroopad 覆盖三大主流桌面系统,支持 Windows、OS X 和 Linux,多种主题样式供你选择,语法标亮支持 54 种编程语言,该工具重点推荐 Ubuntu/Linux 用户使用,点击此处访问 Haroopad 官网

    -

    Haroopad

    +

    Haroopad


    - CuteMarkEd

    CuteMarkEd 是一个基于qt5的跨平台的 Markdown 编辑器,开源的, 提供实时 HTML 预览、数学表达式、源码高亮和PDF导出,点击此处 访问 CuteMarkEd 官网

    -

    CuteMarkEd

    +

    CuteMarkEd


    - MarkPad

    MarkPad 是款开源的 Markdown 编辑器,与 Window 8 风格和谐友好的界面,可以直接在你的博客或者 GitHub 中打开、保存文档,直接将图片粘贴到 Markdown 文档中,点击此处访问 MarkPad 官网

    -

    MarkPad

    +

    MarkPad


    - Cmd Markdown

    作业部落出品,是一款不错的工具和博客平台兼顾的产品,同时支持 Linux、Mac 和 Windows 操作系统,此外还提供 Web 在线创作,社交化批注、智能云同步,最简单的方法,满足多种写作需要,点击此处访问 Cmd Markdown 官网

    -

    Cmd Markdown

    +

    Cmd Markdown


    - FarBox

    同样是一款不错的 Markdown 编辑器和博客平台兼顾的产品,让用户通过Dropbox(现在默认是自己的同步服务器)直接建立个人网站。FarBox编辑器免费,同时支持 Linux、Mac 和 Windows 操作系统,Farbox服务可以免费试用,在本地编辑器内写作自动同步发布在个人博客,对于希望有个人博客但却不愿折腾的小白来说,是个不错的选择,点击此处访问 FarBox 官网

    FarBox

    @@ -589,7 +589,7 @@

    Miu


    - MacDown

    MacDown 引用了许多 Mou 的设计方式,仅支持 Mac ,开源免费,点击此处访问 MacDown 官网

    -

    MacDown

    +

    MacDown


    - Ulysses

    一款由国外开发商 The Soulmen 制作的 Markdown 编辑器。与其它同类应用相比,Ulysses 最大的不同在于,它能根据内置的文件管理器,以及与 iCloud 云服务器的实时同步方案,达到最快捷的文章整理效率,支持OS X , iPad,26人民币每月,14天免费试用,点击此处访问 Ulysses 官网

    Ulysses

    @@ -598,13 +598,13 @@

    Byword


    - MaHua

    一个在线编辑 Markdown 文档的编辑器,小众软件,VIM 快捷键支持,完美兼容 Github 的 Markdown 语法,界面稍许简陋,点击此处访问 MaHua

    -

    MaHua

    +

    MaHua


    - Dillinger

    来自国外的 Markdown 编辑器,漂亮强大,支持md、 html、pdf 文件导出,支持Dropbox、Github、Google Drive、Onedrive 一键保存,点击此处访问 Dillinger

    -

    Dillinger

    +

    Dillinger


    - CSDN

    中国专业IT社区CSDN (Chinese Software Developer Network) 创立于1999年,致力于为中国软件开发者提供知识传播、在线学习、职业发展等全生命周期服务。CSDN的在线编辑器功能强大,支持导出为HTML和md文件,注册账号后即可开始创作,点击此处访问CSDN官网

    -

    CSDN

    +

    CSDN


    - 简书

    简书是一个优质的创作社区,你可以在线创作并发表到社区,是国内优质原创内容输出平台,简书从一开始就已经支持 Markdown 和富文本编辑,是一个为专门为作者打造的平台,点击此处访问简书官网

    简书

    @@ -698,10 +698,10 @@
    -
    + diff --git a/2018/09/09/A06-install-ubuntu18.04/index.html b/2018/09/09/A06-install-ubuntu18.04/index.html index f74a119cf..8c3fab116 100644 --- a/2018/09/09/A06-install-ubuntu18.04/index.html +++ b/2018/09/09/A06-install-ubuntu18.04/index.html @@ -537,11 +537,11 @@

    1.下载安装 VMware Workstation Pro 14

    进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装

    -

    01

    +

    01

    2.下载 Ubuntu 18.04 系统

    进入 Ubuntu 官网,下载最新版 Ubuntu 系统镜像

    -

    02
    03

    -

    3.在 VMware 中创建虚拟机

    打开安装好的 VMware Workstation Pro 14,选择创建新的虚拟机
    04

    -

    在新建虚拟机向导中选择自定义(高级)
    05

    +

    02
    03

    +

    3.在 VMware 中创建虚拟机

    打开安装好的 VMware Workstation Pro 14,选择创建新的虚拟机
    04

    +

    在新建虚拟机向导中选择自定义(高级)
    05

    默认直接下一步,直到出现下图,再选择稍后安装操作系统
    06

    选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本
    07

    更改虚拟机名称及存放位置
    08

    @@ -552,15 +552,15 @@

    选择将虚拟磁盘储存为单个文件
    16

    默认下一步
    17

    点击完成
    18

    -

    此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Ubuntu 64 位,单击 Ubuntu 64 位,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Ubuntu 64 位镜像文件,点击“确定”
    19
    20

    -

    4.在虚拟机上安装 Ubuntu 系统

    单击 Ubuntu 64 位,选择“开启此虚拟机”
    21

    -

    来到欢迎界面,选择好语言,点击“安装 Ubuntu”
    22

    -

    选择键盘布局为“汉语”
    23

    -

    更新和其他软件默认选择即可
    24

    -

    安装类型选择“清除整个磁盘并安装 Ubuntu”,PS: 因为我们是新安装的系统,且在虚拟机中,所以可以选择清除整个磁盘,这个操作不会清除你原来电脑里面的东西
    25
    26

    -

    地区随便,在中国就行,默认即可
    27

    -

    之后设置计算机名,密码
    28

    -

    点击继续稍等一会就安装完成啦
    29

    +

    此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Ubuntu 64 位,单击 Ubuntu 64 位,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Ubuntu 64 位镜像文件,点击“确定”
    19
    20

    +

    4.在虚拟机上安装 Ubuntu 系统

    单击 Ubuntu 64 位,选择“开启此虚拟机”
    21

    +

    来到欢迎界面,选择好语言,点击“安装 Ubuntu”
    22

    +

    选择键盘布局为“汉语”
    23

    +

    更新和其他软件默认选择即可
    24

    +

    安装类型选择“清除整个磁盘并安装 Ubuntu”,PS: 因为我们是新安装的系统,且在虚拟机中,所以可以选择清除整个磁盘,这个操作不会清除你原来电脑里面的东西
    25
    26

    +

    地区随便,在中国就行,默认即可
    27

    +

    之后设置计算机名,密码
    28

    +

    点击继续稍等一会就安装完成啦
    29


    安装过程中可能会出现的一些问题


    1.在虚拟机上安装 Ubuntu 系统的过程中卡死不动

    解决方法:关闭网络,重新安装即可

    @@ -570,7 +570,7 @@

    然后执行:

    1
    sudo apt-get install open-vm*

    重启即可全屏显示

    -

    方法②:在终端输入xrandr,并回车,我们就可以看到很多可以修改的分辨率,选择好分辨率后,比如我们要修改分辨率为 1920x1440 ,则在终端输入 xrandr -s 1920x1440,回车即可,注意 1920x1440 中间是小写字母 x,本人亲测此方法并不是很完美,不能完全适应屏幕
    30

    +

    方法②:在终端输入xrandr,并回车,我们就可以看到很多可以修改的分辨率,选择好分辨率后,比如我们要修改分辨率为 1920x1440 ,则在终端输入 xrandr -s 1920x1440,回车即可,注意 1920x1440 中间是小写字母 x,本人亲测此方法并不是很完美,不能完全适应屏幕
    30

    方法③:安装 VMware Tools:
    1、进入 Ubuntu 系统后,点击虚拟机上的【虚拟机】—>【安装 VMware Tools】,回到桌面即可看到一个 VMware Tools 的 图标
    2、复制 VMwareTools-10.0.10-4301679.tar.gz(版本根据自己的实际情况而定)到 home 目录下, 用命令 tar -xzvf VMwareTools-10.0.10-4301679.tar.gz 进行解压
    3、解压后 cd vmware_tools_distrib,打开终端
    4、输入“sudo ./vmware-install.pl”,输入用户密码后开始安装
    5、接下来会有很多地方需要你按 Enter或者 Yes
    6、当你看到出现 —the vmware team 的字样后就可以关闭窗口了,此时窗口就会自动全屏了,如果没有全屏,重启过后就可以了
    7、若还没有全屏显示,则将虚拟机的【查看】—>【自动调整大小】—>【自适应客户机】,都选上,即可实现全屏

    @@ -659,10 +659,10 @@
    -
    + diff --git a/2019/02/10/A18-free-cdn/index.html b/2019/02/10/A18-free-cdn/index.html index f608e1662..4f1221d63 100644 --- a/2019/02/10/A18-free-cdn/index.html +++ b/2019/02/10/A18-free-cdn/index.html @@ -540,12 +540,12 @@

    CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。——百度百科

    放在Github的资源在国内加载速度比较慢,因此需要使用CDN加速来优化网站打开速度,jsDelivr + Github便是免费且好用的CDN,非常适合博客网站使用。


    -

    1、新建Github仓库

    01

    -

    2、克隆Github仓库到本地

    点击 Clone or download,一键复制仓库地址
    04

    +

    1、新建Github仓库

    01

    +

    2、克隆Github仓库到本地

    点击 Clone or download,一键复制仓库地址
    04

    在本地目录右键 Git Bash Here,执行以下命令:

    1
    git clone 一键复制的仓库地址

    3、上传资源

    复制需要上传的资源到本地git仓库(注:jsDelivr不支持加载超过20M的资源),在本地git仓库目录下右键 Git Bash Here,执行以下命令:

    1
    2
    3
    4
    git status                    //查看状态
    git add . //添加所有文件到暂存区
    git commit -m '第一次提交' //把文件提交到仓库
    git push //推送至远程仓库

    -

    4、发布仓库

    点击release发布
    02

    -

    自定义发布版本号
    03

    +

    4、发布仓库

    点击release发布
    02

    +

    自定义发布版本号
    03

    5、通过jsDelivr引用资源

    使用方法:https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径
    例如:https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@1.0/images/trhx.png
        https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.1/css/style.css
        https://cdn.jsdelivr.net/gh/moezx/cdn@3.1.3//The%20Pet%20Girl%20of%20Sakurasou.mp4

    注意:版本号不是必需的,是为了区分新旧资源,如果不使用版本号,将会直接引用最新资源,除此之外还可以使用某个范围内的版本,查看所有资源等,具体使用方法如下:

      @@ -649,10 +649,10 @@
      -
      + diff --git a/2019/04/14/A20-install-deepin15.9/index.html b/2019/04/14/A20-install-deepin15.9/index.html index 5c759b7fe..2576eea6f 100644 --- a/2019/04/14/A20-install-deepin15.9/index.html +++ b/2019/04/14/A20-install-deepin15.9/index.html @@ -538,10 +538,10 @@

      Deepin是由武汉深之度科技有限公司开发的Linux发行版,个人认为其界面设计非常美观,而且作为国产操作系统,值得我们去体验和支持!


      -

      1.下载安装 VMware Workstation Pro 15

      进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装
      001

      -

      2.下载 Deepin15.9 系统

      进入 deepin 官网,下载最新版 deepin 系统镜像
      002

      -

      3.在 VMware 中创建虚拟机

      打开安装好的 VMware Workstation Pro 15,选择创建新的虚拟机
      003
      在新建虚拟机向导中选择自定义(高级):
      004
      默认直接下一步,直到出现下图,再选择稍后安装操作系统:
      005
      选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本:
      006
      更改虚拟机名称及存放位置:
      007
      接下来为虚拟机指定处理器数量、分配内存(太大了可能会导致卡顿,太小了也不好,推荐内存大小即可)一直选择默认即可,选择磁盘时,选择创建新虚拟磁盘:
      008
      选择将虚拟磁盘储存为单个文件:
      009
      默认下一步:
      010
      点击完成:
      011
      此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Deepin,单击 Deepin,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Deepin 15.9 镜像文件,点击“确定”
      012
      013

      -

      4.在虚拟机上安装 Deepin 系统

      单击 Deepin,选择“开启此虚拟机”
      014
      接下来就是选择语言、创建用户、选择时区、指定磁盘等过程:
      015
      016
      017
      018
      安装完成后:
      019
      可以看见界面还是相当美观的,系统也自带了深度的一些软件,比如深度录屏,深度录音,深度影院,深度计算器等等的一些小工具,作为国产操作系统,个人觉得已经非常优秀了,值得去体验!

      +

      1.下载安装 VMware Workstation Pro 15

      进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装
      001

      +

      2.下载 Deepin15.9 系统

      进入 deepin 官网,下载最新版 deepin 系统镜像
      002

      +

      3.在 VMware 中创建虚拟机

      打开安装好的 VMware Workstation Pro 15,选择创建新的虚拟机
      003
      在新建虚拟机向导中选择自定义(高级):
      004
      默认直接下一步,直到出现下图,再选择稍后安装操作系统:
      005
      选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本:
      006
      更改虚拟机名称及存放位置:
      007
      接下来为虚拟机指定处理器数量、分配内存(太大了可能会导致卡顿,太小了也不好,推荐内存大小即可)一直选择默认即可,选择磁盘时,选择创建新虚拟磁盘:
      008
      选择将虚拟磁盘储存为单个文件:
      009
      默认下一步:
      010
      点击完成:
      011
      此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Deepin,单击 Deepin,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Deepin 15.9 镜像文件,点击“确定”
      012
      013

      +

      4.在虚拟机上安装 Deepin 系统

      单击 Deepin,选择“开启此虚拟机”
      014
      接下来就是选择语言、创建用户、选择时区、指定磁盘等过程:
      015
      016
      017
      018
      安装完成后:
      019
      可以看见界面还是相当美观的,系统也自带了深度的一些软件,比如深度录屏,深度录音,深度影院,深度计算器等等的一些小工具,作为国产操作系统,个人觉得已经非常优秀了,值得去体验!



      @@ -629,10 +629,10 @@
      -
      + diff --git a/2019/05/14/A22-eclipse-connects-to-sql/index.html b/2019/05/14/A22-eclipse-connects-to-sql/index.html index af6499999..afbb827bf 100644 --- a/2019/05/14/A22-eclipse-connects-to-sql/index.html +++ b/2019/05/14/A22-eclipse-connects-to-sql/index.html @@ -541,30 +541,34 @@

      Eclipse Photon Release (4.8.0)
      JDK-10.0.2
      SQL Server 2012

    1.配置 SQL Server 2012

    打开 SQL Server Management Studio,使用 SQL Server 身份验证 登录:

    -

    001.png

    +

    001

    如果在安装 SQL Server 2012 时选用了Windows身份验证登录方式,则需要重新设置,设置方法参考:《SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】》

    登录成功后,打开 SQL Server 配置管理器:

    -

    002.png

    +

    002

    在左边找到 SQL Server 网络配置,点击【你的数据库名】的协议,将右边栏的 Shared Memory、Named Pipes、TCP/IP 全部右键选择启用

    -

    003.png
    004.png

    +

    003

    +

    004

    双击 TCP/IP(或者右键选择属性),选择【IP地址】,将【IP1】和【IP10】的【IP地址】设为 127.0.0.1

    -

    005.png

    +

    005

    将所有【IPx】(IP1、IP10、IP11、IP12等)的【已启用】设为

    -

    006.png

    +

    006

    下拉到窗口底部,将 【IPAll】 中的【TCP端口】设成 1433,其余不变

    -

    007.png

    +

    007

    2.开启 Telnet 服务

    打开【控制面板】,选择【程序】,点击【启用或关闭 Windows 功能】,找到【Telnet Client】勾选并保存,Windows 7 或者以下的版本则勾选【Telnet 服务器】和【Telnet 客户端

    -

    008.png
    009.png

    +

    008

    +

    009

    3.测试1433端口是否打开

    运行cmd,输入 telnet 127.0.0.1 1433,若提示连接失败,则说明1433端口没有打开,需要重新进行以上配置,若连接成功,则显示如下:

    -

    010.png

    +

    010

    4.下载JDBC

    点击此处下载各个版本JDBC,不同版本的JDBC驱动程序适用的JAR不同,与不同版本的SQL兼容性也不同,具体参考《Microsoft SQL Server JDBC 驱动程序支持矩阵》,比如使用 SQL Server 2012 我们可以下载6.0的版本,下载sqljdbc_6.0.8112.200_chs.tar.gz文件,解压后可以找到sqljdbc41.jarsqljdbc42.jar文件,使用时要注意自己JDK是哪个版本的,1.80以上的则对应 sqljdbc42.jar 类库

    5.Eclipse 连接 SQL Server

    将 sqljdbc41.jar 或者 sqljdbc42.jar 放到一个文件夹下,打开 Eclipse,在需要连接数据库的项目里,右键【src】,选择【Build Path】、【Configure Build Path…】,在弹出的窗口选择【Libraries】,选择【Modulepath】,单击【Add External JARs…】,找到下载的 sqljdbc41.jar 或者 sqljdbc42.jar 文件并打开,然后【Apply and Close】保存

    -

    011.png
    012.png
    013.png

    +

    011

    +

    012

    +

    013

    6.测试连接

    打开 SQL Server 2012,在其中新建数据库 test

    -

    014.png

    +

    014

    Eclipse中,在项目下新建一个package,再新建一个class,用于测试数据库的连接:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package test;
    import java.sql.*;
    public class Main {
    public static void main(String [] args)
    {
    String driverName="com.microsoft.sqlserver.jdbc.SQLServerDriver";
    String dbURL="jdbc:sqlserver://localhost:1433;DatabaseName=test"; //要连接的数据库名
    String userName="sa"; //数据库用户名
    String userPwd="000000"; //数据库密码
    try
    {
    Class.forName(driverName);
    Connection dbConn=DriverManager.getConnection(dbURL,userName,userPwd);
    System.out.println("连接数据库成功");
    }
    catch(Exception e)
    {
    e.printStackTrace();
    System.out.print("连接失败");
    }
    }
    }

    如果以上所有操作正确,就能成功连接数据库了:

    -

    015.png

    +

    015



    @@ -652,10 +656,10 @@
    -
    + diff --git a/2019/07/31/A26-hexo-add-https/index.html b/2019/07/31/A26-hexo-add-https/index.html index 4668fcbbd..7fafcce56 100644 --- a/2019/07/31/A26-hexo-add-https/index.html +++ b/2019/07/31/A26-hexo-add-https/index.html @@ -551,16 +551,16 @@

    – 前言

    GitHub Pages 自带的域名(xxx.github.io)支持开启 https 服务,可以在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】即可,但是如果你设置了自定义域名的话,就比较复杂了,因为 hexo 博客是托管在 GitHub 上的,没有自己的服务器,因此也不支持上传 SSL 证书,从2018年5月1日起,GitHub官方也支持自定义域名开启https了,实现方法可参考我的文章:《利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS》,另外一种方法就是利用 Cloudflare 的 CDN 中转来启用 HTTPS,这种方法的弊端就是国内访问速度可能会变慢,本文主要讲述这种方法

    Cloudflare 是一家美国的跨国科技企业,以向客户提供网站安全管理、性能优化及相关的技术支持为主要业务,它提供了免费的 https 服务,注意不是应用SSL证书,实现原理:用户到CDN服务器的连接为 https 方式,而CDN服务器到 GithubPages 服务器的连接为 http 方式,在CDN服务器那里加上反向代理

    – 注册 Cloudflare

    Cloudflare官网 注册账号

    -
    01.png
    +
    01.jpg

    – 添加站点

    添加你的站点,一直下一步即可

    -
    02.png


    03.png

    如果你已经在域名服务商那里解析过域名的话,之后就会出现你域名的解析列表,如果还没有解析过,可以参考《为hexo博客配置个性域名》

    04.png
    +
    02.jpg


    03.jpg

    如果你已经在域名服务商那里解析过域名的话,之后就会出现你域名的解析列表,如果还没有解析过,可以参考《为hexo博客配置个性域名》

    04.jpg

    –修改DNS

    点击下一步 Cloudflare 会提供给你两个 DNS 地址

    -
    05.png

    到域名服务商那里修改DNS,以阿里云为例,依次选择【控制台】-【域名】,选择你的域名,点击【管理】-【修改DNS】,将上面 Cloudflare 提供的两个 DNS 地址填进去,会过几分钟才生效

    08.png


    09.png


    10.png
    +
    05.jpg

    到域名服务商那里修改DNS,以阿里云为例,依次选择【控制台】-【域名】,选择你的域名,点击【管理】-【修改DNS】,将上面 Cloudflare 提供的两个 DNS 地址填进去,会过几分钟才生效

    08.jpg


    09.jpg


    10.jpg

    –开启 HTTPS

    在 Cloudflare 管理页面,点击【Crypto】选项,选择 SSL 的模式为【full】,注意:在CloudFlare 上激活站点后,可能需要24小时才能颁发新证书,耐心等待即可

    -
    07.png
    +
    07.jpg

    关于三种模式 Flexible、Full、Full (Strict) 的区别:

      @@ -573,7 +573,7 @@

    至此,我们的域名就支持 https 访问了,但是当用户输入 http://xxxxxx 访问时,浏览器依旧会以 http 协议来访问,并不会跳转到 https,这时候就需要利用重定向来解决了

    –重定向强制 HTTPS

    Cloudflare 提供了一个名叫 Page Rules 的页面规则的功能,我们可以利用此功能对 URL 做一些处理,当用户访问是 HTTP 的时候重定向到 HTTPS,点击【Page Rules】选项,点击【Create Page Rules】,新建如下规则并保存即可

    -
    06.png
    +
    06.jpg

    现在我们的 Hexo 博客就实现了全站 HTTPS!

    @@ -663,10 +663,10 @@
    -
    + diff --git a/2019/08/01/A27-image-hosting/index.html b/2019/08/01/A27-image-hosting/index.html index b75b810e0..926d083d7 100644 --- a/2019/08/01/A27-image-hosting/index.html +++ b/2019/08/01/A27-image-hosting/index.html @@ -552,14 +552,14 @@

    因此,GitHub 图床是个不错的选择,利用 jsDelivr CDN 加速访问(jsDelivr 是一个免费开源的 CDN 解决方案),PicGo 工具一键上传,操作简单高效,GitHub 和 jsDelivr 都是大厂,不用担心跑路问题,不用担心速度和容量问题,而且完全免费,可以说是目前免费图床的最佳解决方案!


    – 新建GitHub仓库

    登录/注册GitHub,新建一个仓库,填写好仓库名,仓库描述,根据需求选择是否为仓库初始化一个README.md描述文件

    -
    01


    02
    +
    01


    02

    – 生成一个Token

    在主页依次选择【Settings】-【Developer settings】-【Personal access tokens】-【Generate new token】,填写好描述,勾选【repo】,然后点击【Generate token】生成一个Token,注意这个Token只会显示一次,自己先保存下来,或者等后面配置好PicGo后再关闭此网页

    -
    03


    04


    05


    06


    07
    +
    03


    04


    05


    06


    07

    – 配置PicGo

    前往下载PicGo,安装好后开始配置图床

    -
    08
    +
    08
    • 设定仓库名:按照【用户名/图床仓库名】的格式填写

      @@ -663,10 +663,10 @@
      -
      + diff --git a/2019/08/23/A23-beian/index.html b/2019/08/23/A23-beian/index.html index ea212807a..e5dc98e5c 100644 --- a/2019/08/23/A23-beian/index.html +++ b/2019/08/23/A23-beian/index.html @@ -536,7 +536,7 @@
      -

      Website-Approve.png

      +

      Website-Approve

      网站备案分为ICP备案和公安备案

      • ICP备案:ICP备案的目的就是为了防止在网上从事非法的网站经营活动,打击不良互联网信息的传播,如果网站不备案的话,很有可能被查处以后关停。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》条例,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款或关闭。

        @@ -544,12 +544,12 @@
      • 公安备案:网站备案是根据国家法律法规需要网站的所有者向国家有关部门申请的备案,公安局备案是其中一种。公安局备案一般按照各地公安机关指定的地点和方式进行,操作流程会比ICP备案流程简单,主要是已登记为主。

      -

      以百度官网为例,其中京公安网备11000002000001就是公安备案,京ICP证030173号就是ICP备案
      01.png

      -

      – ICP备案

      一般在域名服务商那里都会有代备案系统,下面以阿里云为例,进入备案系统:
      02.png

      -

      1、填写信息验证备案类型

      备案主办单位填写,个人就选个人,企业就选企业,按照实际信息填写:
      03.png

      -

      2、产品验证

      对搭建备案网站的云服务器进行验证,如果你在阿里云购买了相关产品,就选择相应的产品类型和实例进行验证,也可以勾选已有备案服务号,填写服务号进行验证,备案服务号可以通过备案控制台进行申请,具体操作可以参考官方文档《申请备案服务号》,也有的小伙伴没有在任何地方购买过服务器等相关产品,比如单纯搭建一个 Github Pages + Hexo 轻量级的个人博客,这种博客没有后端,不需要服务器,但是要备案怎么办?这种情况也好解决,去某宝买一个服务号就行了。
      04.png
      05.png

      -

      3、填写网站信息

      填写网站信息以及办理备案的个人或者单位的真实信息,在填写网站名称的时候要特别注意!特别注意!特别注意!不满足要求的话是会被打回的!不能使用姓名、地名、成语、不能包含公司、组织等企业性质的词语……具体要求可以参考官方文档《填写主体信息和网站信息》
      06.png
      07.png

      -

      4、上传资料

      根据要求,上传证件照片或证件彩色扫描件。身份证好说,拍好了上传就行了,注意《网站备案信息真实性核验单》需要你下载并打印在一张A4纸上,使用黑色签字笔填写,不能涂改,具体可参照所给的示例进行填写,填写完成后再拍照上传。企业网站类似,提交备案后会在一个工作日内进行初审。
      08.png
      09.jpg
      10.png

      +

      以百度官网为例,其中京公安网备11000002000001就是公安备案,京ICP证030173号就是ICP备案
      01

      +

      – ICP备案

      一般在域名服务商那里都会有代备案系统,下面以阿里云为例,进入备案系统:
      02

      +

      1、填写信息验证备案类型

      备案主办单位填写,个人就选个人,企业就选企业,按照实际信息填写:
      03

      +

      2、产品验证

      对搭建备案网站的云服务器进行验证,如果你在阿里云购买了相关产品,就选择相应的产品类型和实例进行验证,也可以勾选已有备案服务号,填写服务号进行验证,备案服务号可以通过备案控制台进行申请,具体操作可以参考官方文档《申请备案服务号》,也有的小伙伴没有在任何地方购买过服务器等相关产品,比如单纯搭建一个 Github Pages + Hexo 轻量级的个人博客,这种博客没有后端,不需要服务器,但是要备案怎么办?这种情况也好解决,去某宝买一个服务号就行了。
      04
      05

      +

      3、填写网站信息

      填写网站信息以及办理备案的个人或者单位的真实信息,在填写网站名称的时候要特别注意!特别注意!特别注意!不满足要求的话是会被打回的!不能使用姓名、地名、成语、不能包含公司、组织等企业性质的词语……具体要求可以参考官方文档《填写主体信息和网站信息》
      06
      07

      +

      4、上传资料

      根据要求,上传证件照片或证件彩色扫描件。身份证好说,拍好了上传就行了,注意《网站备案信息真实性核验单》需要你下载并打印在一张A4纸上,使用黑色签字笔填写,不能涂改,具体可参照所给的示例进行填写,填写完成后再拍照上传。企业网站类似,提交备案后会在一个工作日内进行初审。
      08
      09.jpg
      10

      5、人脸核验或幕布拍照核验

      根据不同地域管局要求及核验平台的支持情况,使用人脸识别进行核验,或者申请专用幕布进行幕布拍照核验

      @@ -573,18 +573,18 @@
      -

      以幕布拍照核验为例,如果你没有阿里云的幕布,就需要申请幕布(免费的),邮寄很快,大约两三天就到了,等收到幕布后,按照要求进行拍照,一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!不合格依旧会被打回!拍照完成后上传即可。
      11.png
      12.png
      13.png

      +

      以幕布拍照核验为例,如果你没有阿里云的幕布,就需要申请幕布(免费的),邮寄很快,大约两三天就到了,等收到幕布后,按照要求进行拍照,一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!不合格依旧会被打回!拍照完成后上传即可。
      11
      12
      13

      6、提交管局、短信核验

      当照片审核通过后,就会提交到管局,工信部要求部分省市成为手机号码短信核验试点省市,相应省市的用户在阿里云备案平台提交备案申请且初审完成后,会收到工信部发送的核验短信,短信包含验证码和验证地址,需要在收到短信的24小时内完成短信核验,备案申请才能进入管局审核。
      需短信核验省份:

      • 2017年12月18日起:天津、甘肃、西藏、宁夏、海南、新疆、青海被列为试点省份。
      • 2018年9月10日起:浙江、四川、福建、陕西、重庆、广西、云南被列为试点省份。
      • 2018年9月24日起:山东、河南、安徽、湖南、山西、黑龙江、内蒙古、湖北被列为试点省份。
      -

      14.png

      -

      7、ICP备案完成

      整个备案过程中会有阿里云的客服打电话给你,进行信息确认,备案申请信息成功提交管局系统后,管局审核一般为 3 - 20 个工作日(亲测很快,不到一个周就通过了),审核通过后会收到阿里云的邮件通知。
      15.png

      +

      14

      +

      7、ICP备案完成

      整个备案过程中会有阿里云的客服打电话给你,进行信息确认,备案申请信息成功提交管局系统后,管局审核一般为 3 - 20 个工作日(亲测很快,不到一个周就通过了),审核通过后会收到阿里云的邮件通知。
      15

      – 公安备案

      公安备案个人觉得比ICP备案还要麻烦,自己在公安备案的时候,最开始申请了一个月也没给我处理(大概是地方原因,所在的市比较小,估计都没几个人办过网站,网警也不太负责),与ICP备案最大的不同,如果你是交互式网站的话,公安备案是需要你去公安机关当面审核的,这也是比较麻烦的一点。

      -

      1、用户注册、登录

      登录全国互联网安全管理服务平台,选择联网备案登录,注册账号并登录
      16.png

      -

      2、新办网站备案申请

      点击新办网站申请,按实填写网站开办主体,上传身份证正反照和手持身份证件照。
      17.png
      18.png

      +

      1、用户注册、登录

      登录全国互联网安全管理服务平台,选择联网备案登录,注册账号并登录
      16

      +

      2、新办网站备案申请

      点击新办网站申请,按实填写网站开办主体,上传身份证正反照和手持身份证件照。
      17
      18

      3、填写网站基本信息

      按实填写网站基本信息,需要注意的地方:

      IP:IP地址为阿里云/腾讯云的公网IP地址,请不要填写内网IP。

      域名证书:以阿里云为例,进入【域名控制台】,点击域名后面的【管理】,选择【域名证书下载】即可,其它服务商类似。

      @@ -603,9 +603,9 @@

    也可以通过点击后面的查询网络接入\域名注册服务商直接选择相应服务商,其他服务商类似

    服务类型:交互式服务指:为互联网用户提供信息发布、交流互动等服务,包括但不限于论坛、博客、微博、网络购物、网上支付等服务类型,此项选择是否提供互联网交互服务将会直接影响到后面是否需要去公安局当面核验,若选择,当地网警会打电话叫你去公安局当面核验,还需要填写《交互式服务安全检查表》等各种文件,总之是比较麻烦的,个人小网站,博客什么的建议选择,选择www服务,这样的话不用去当面核验,审核下来也比较快,企业单位用户建议选择交互式。

    -

    其他信息如实填写即可!
    19.png

    -

    4、填写网站负责人信息

    填写网站安全负责人和网站应急联络人相关信息,网站应急联络人直接勾选同主体负责人后会自动填入。
    20.png

    -

    5、同意责任书并提交审核

    《互联网信息服务单位网络安全责任告知书》有30秒的强制阅读时间,建议认真阅读一下告知书的内容。然后勾选我已阅读,点击提交即可。随后可以看到审核状态,不同地区政策有所不同,会有当地的网警联系网站负责人的,审核通过后记得在网站首页底部张贴公安机关核发的备案图标!
    21.png
    22.png

    +

    其他信息如实填写即可!
    19

    +

    4、填写网站负责人信息

    填写网站安全负责人和网站应急联络人相关信息,网站应急联络人直接勾选同主体负责人后会自动填入。
    20

    +

    5、同意责任书并提交审核

    《互联网信息服务单位网络安全责任告知书》有30秒的强制阅读时间,建议认真阅读一下告知书的内容。然后勾选我已阅读,点击提交即可。随后可以看到审核状态,不同地区政策有所不同,会有当地的网警联系网站负责人的,审核通过后记得在网站首页底部张贴公安机关核发的备案图标!
    21
    22



    @@ -693,10 +693,10 @@
    -
    + diff --git a/2019/08/23/A25-SB/index.html b/2019/08/23/A25-SB/index.html index 5d4147188..7e039e74a 100644 --- a/2019/08/23/A25-SB/index.html +++ b/2019/08/23/A25-SB/index.html @@ -545,7 +545,7 @@

    随便提一句,这家伙连我的情侣博客一起刷的,真是让人大跌眼镜啊,估计自己没女朋友吧,见不得别人好,悲催啊,不知道又是哪个学校,哪个公司,哪个家庭摊上了这种垃圾。

    -
    01
    +
    01

    02

    @@ -636,10 +636,10 @@
    -
    + diff --git a/2019/08/23/A30-Python3-spider-C02/index.html b/2019/08/23/A30-Python3-spider-C02/index.html index ecb2a098d..50e71c968 100644 --- a/2019/08/23/A30-Python3-spider-C02/index.html +++ b/2019/08/23/A30-Python3-spider-C02/index.html @@ -559,9 +559,13 @@

    【2.3.2】 二进制数据抓取

    以抓取 GitHub 站点图标为例:

    1
    2
    3
    4
    5
    import requests

    r = requests.get("https://github.com/favicon.ico")
    with open('favicon.ico', 'wb') as f:
    f.write(r.content)

    该代码将会保存站点图标到本地,其他的,比如音频,视频文件都是由二进制码组成的,皆可使用该方法

    【2.3.3】 添加 headers

    headers 的作用:部分页面禁止 Python 爬虫对其进行爬取,而添加 headers 就可以模拟成浏览器取访问网站,实现数据的爬取,headers 可以在任意网页 F12 检查控制台里面找到,headers 最重要的是 “User-Agent” 字段

    -


    01

    以为例知乎,只有加了 headers 才能正常爬取,否则会返回 400 Bad Request 没有任何数据

    1
    2
    3
    4
    5
    6
    import requests

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }
    r = requests.get("https://www.zhihu.com/explore", headers=headers)
    print(r.text)

    +
    01
    + +

    以为例知乎,只有加了 headers 才能正常爬取,否则会返回 400 Bad Request 没有任何数据

    +
    1
    2
    3
    4
    5
    6
    import requests

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }
    r = requests.get("https://www.zhihu.com/explore", headers=headers)
    print(r.text)

    【2.4】 requests 构建 POST 请求

    示例:

    1
    2
    3
    4
    5
    import requests

    data = {'name': 'TRHX', 'age': '20'}
    r = requests.post("http://httpbin.org/post", data=data)
    print(r.text)

    -

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "age": "22",
    "name": "germey"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "18",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }

    +

    输出结果:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "age": "22",
    "name": "germey"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "18",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }

    有关 POST 和 GET 两种请求的一些区别:

    • POST 更加安全,不会作为 URL 的一部分,不会被缓存,保存在服务器日志、以及浏览器浏览记录中;
    • @@ -571,9 +575,12 @@
    • POST 查询参数在 WebForms 保存,GET 查询参数在 QueryString 保存;
    • POST 用数据的修改和写入,GET 一般用于搜索排序和筛选之类的操作。
    -

    【2.5】 requests 高级用法

    【2.5.1】 上传文件

    示例:

    1
    2
    3
    4
    5
    import requests

    files = {'file': open('test.png', 'rb')}
    r = requests.post('http://httpbin.org/post', files=files)
    print(r.text)

    -

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "args": {},
    "data": "",
    "files": {
    "file": "data:application/octet-stream;base64,iVBOR......"
    },
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "81383",
    "Content-Type": "multipart/form-data; boundary=e36a8686cd77c79dc02bfe9d1b010f08",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }

    -

    【2.5.2】 使用 Cookies

    对于需要登录后才能获取数据的网页,可以将账号登录的 Cookies 添加到 headers 来实现网页登录爬取,Cookies 可以抓包获取,代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests

    headers = {
    'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
    }
    r = requests.get('https://www.zhihu.com', headers=headers)
    print(r.text)

    +

    【2.5】 requests 高级用法

    【2.5.1】 上传文件

    示例:

    +
    1
    2
    3
    4
    5
    import requests

    files = {'file': open('test.png', 'rb')}
    r = requests.post('http://httpbin.org/post', files=files)
    print(r.text)
    +

    输出结果:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "args": {},
    "data": "",
    "files": {
    "file": "data:application/octet-stream;base64,iVBOR......"
    },
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "81383",
    "Content-Type": "multipart/form-data; boundary=e36a8686cd77c79dc02bfe9d1b010f08",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }
    +

    【2.5.2】 使用 Cookies

    对于需要登录后才能获取数据的网页,可以将账号登录的 Cookies 添加到 headers 来实现网页登录爬取,Cookies 可以抓包获取,代码示例:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests

    headers = {
    'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
    }
    r = requests.get('https://www.zhihu.com', headers=headers)
    print(r.text)

    【2.5.3】 会话维持

    • 背景介绍:利用 get() 或者 post() 方法来模拟网页请求,相当于是不同的会话,可以理解为用两个浏览器打开了不同的网页;

    • @@ -584,19 +591,25 @@
      1
      2
      3
      4
      5
      6
       import requests

      s = requests.Session()
      s.get('http://httpbin.org/cookies/set/number/123456789')
      r = s.get('http://httpbin.org/cookies')
      print(r.text)
    -

    输出结果成功获取到设置的 cookies:

    1
    2
    3
    4
    5
    {
    "cookies": {
    "number": "123456789"
    }
    }

    +

    输出结果成功获取到设置的 cookies:

    +
    1
    2
    3
    4
    5
    {
    "cookies": {
    "number": "123456789"
    }
    }

    【2.5.4】 SSL 证书验证

    SSL 证书是数字证书的一种,由受信任的数字证书颁发机构 CA 在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,网站带有 HTTPS 就表明有 SSL 证书

    -

    requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,verify 参数可以控制是否检查此证书。如果不加 verify 参数,默认为 True,会自动验证。当一个页面的 SSL 证书没有被官方机构认证时,打开页面就会提示“您的连接不是私密连接”,如果没有设置 verify 参数,将会报以下错误:

    1
    requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)

    -

    设置 verify 参数代码示例:

    1
    2
    3
    4
    import requests

    response = requests.get('https://www.itrhx.com', verify=False)
    print(response.text)

    -

    【2.5.5】 设置代理

    为什么要设置代理:某些网页有反爬虫机制,频繁请求网页就会出现验证码等,还有可能直接封掉 IP,导致爬取失败;这种情况下就可以设置 proxies 参数。
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    proxies = {
    'http': 'http://10.10.1.10:1010',
    'https': 'http://10.10.1.10:1020',
    }

    requests.get('https://www.itrhx.com', proxies=proxies)

    +

    requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,verify 参数可以控制是否检查此证书。如果不加 verify 参数,默认为 True,会自动验证。当一个页面的 SSL 证书没有被官方机构认证时,打开页面就会提示“您的连接不是私密连接”,如果没有设置 verify 参数,将会报以下错误:

    +
    1
    requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)
    +

    设置 verify 参数代码示例:

    +
    1
    2
    3
    4
    import requests

    response = requests.get('https://www.itrhx.com', verify=False)
    print(response.text)
    +

    【2.5.5】 设置代理

    为什么要设置代理:某些网页有反爬虫机制,频繁请求网页就会出现验证码等,还有可能直接封掉 IP,导致爬取失败;这种情况下就可以设置 proxies 参数。
    示例:

    +
    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    proxies = {
    'http': 'http://10.10.1.10:1010',
    'https': 'http://10.10.1.10:1020',
    }

    requests.get('https://www.itrhx.com', proxies=proxies)

    免费代理可在西刺代理找到

    【2.5.6】 超时设置

    与 urllib.request.urlopen() 类似,requests 也可以设置 timeout 参数,请求分为两个阶段:连接和读取

    设置连接和读取时间总和:

    1
    2
    3
    4
    import requests

    r = requests.get('https://www.itrhx.com', timeout=1)
    print(r.status_code)
    -

    分别设置连接和读取时间:

    1
    2
    3
    4
    import requests

    r = requests.get('https://www.itrhx.com', timeout=(5, 10))
    print(r.status_code)

    -

    永久等待:

    1
    2
    3
    4
    5
    6
    import requests

    # 两种方法实现
    # r = requests.get('https://www.itrhx.com')
    r = requests.get('https://www.itrhx.com', timeout=None)
    print(r.status_code)

    +

    分别设置连接和读取时间:

    +
    1
    2
    3
    4
    import requests

    r = requests.get('https://www.itrhx.com', timeout=(5, 10))
    print(r.status_code)
    +

    永久等待:

    +
    1
    2
    3
    4
    5
    6
    import requests

    # 两种方法实现
    # r = requests.get('https://www.itrhx.com')
    r = requests.get('https://www.itrhx.com', timeout=None)
    print(r.status_code)


    @@ -684,10 +697,10 @@
    -
    + diff --git a/2019/09/03/A40-Python3-spider-C10/index.html b/2019/09/03/A40-Python3-spider-C10/index.html index 7353a430d..33fd3e913 100644 --- a/2019/09/03/A40-Python3-spider-C10/index.html +++ b/2019/09/03/A40-Python3-spider-C10/index.html @@ -552,7 +552,7 @@

    该段代码实现了在 spiders 数据库里创建了一个名为 students 的表,包含 id、name、age 三个字段,类型依次为 varchar、varchar、int

    【10.4】插入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import pymysql

    id = '17110105'
    user = 'TRH'
    age = 20
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'
    try:
    cursor.execute(sql, (id, user, age))
    db.commit()
    except:
    db.rollback()
    db.close()

    commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏,使用查询语句可以看到已经插入的数据:

    -
    01
    +
    01

    进阶操作:将需要插入的数据构造成一个字典,这样的做法可以让插入方法无需改动,只需要传入一个动态变化的字典就行了,改写原来的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import pymysql

    data = {
    'id': '17110105',
    'name': 'TRH',
    'age': 20
    }
    table = 'students'
    keys = ', '.join(data.keys())
    values = ', '.join(['%s']*len(data))
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
    try:
    cursor.execute(sql, tuple(data.values()))
    print('数据插入成功!')
    db.commit()
    except:
    print('数据插入失败!')
    db.rollback()
    db.close()
    @@ -574,7 +574,7 @@

    获取文章标题和对应的 URL 并将其储存到 MySQL 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import requests
    import pymysql
    from bs4 import BeautifulSoup

    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')
    cursor = db.cursor()

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    }
    url = "https://blog.csdn.net/qq_36759224"

    request = requests.get(url, headers=headers)
    soup = BeautifulSoup(request.text, 'lxml')
    title_list = soup.find_all('h4')
    for list in title_list:
    s = list.a.text.strip()
    title = s.replace('原', '')
    url = list.a['href'].strip()
    # print(title + url)
    cursor.execute('INSERT INTO article (title, url) VALUES (%s, %s)', (title, url))
    db.commit()
    print('数据写入完毕!')
    db.close()

    在命令行中使用 SELECT * FROM article; 命令可以查看到数据已经成功获取并储存到了数据库中:

    -
    02
    +
    02


    @@ -662,10 +662,10 @@
    -
    + diff --git a/2019/09/14/A46-Python3-spider-C16/index.html b/2019/09/14/A46-Python3-spider-C16/index.html index d68751c8b..c0912ac56 100644 --- a/2019/09/14/A46-Python3-spider-C16/index.html +++ b/2019/09/14/A46-Python3-spider-C16/index.html @@ -1491,9 +1491,9 @@ Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】
     上一页

    -

    diff --git a/2019/09/16/A47-hexo-deployed-to-github-and-coding/index.html b/2019/09/16/A47-hexo-deployed-to-github-and-coding/index.html index 6c8fbf36a..cdba6db72 100644 --- a/2019/09/16/A47-hexo-deployed-to-github-and-coding/index.html +++ b/2019/09/16/A47-hexo-deployed-to-github-and-coding/index.html @@ -34,7 +34,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS | TRHX'S BLOG + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS | TRHX'S BLOG @@ -426,7 +426,7 @@
    - +
    @@ -434,7 +434,7 @@

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    @@ -687,10 +687,10 @@
    -
    + @@ -712,7 +712,7 @@ @@ -724,7 +724,7 @@ @@ -744,7 +744,7 @@ @@ -852,7 +852,7 @@ diff --git a/2019/09/17/A48-submit-search-engine-inclusion/index.html b/2019/09/17/A48-submit-search-engine-inclusion/index.html index ba8d740ff..e9b894059 100644 --- a/2019/09/17/A48-submit-search-engine-inclusion/index.html +++ b/2019/09/17/A48-submit-search-engine-inclusion/index.html @@ -898,9 +898,9 @@

    -

    diff --git a/archives/2019/09/index.html b/archives/2019/09/index.html index 53ecaba66..63f7e8e0e 100644 --- a/archives/2019/09/index.html +++ b/archives/2019/09/index.html @@ -1239,14 +1239,14 @@
    - +

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    diff --git a/archives/2019/page/2/index.html b/archives/2019/page/2/index.html index 409021133..6e214d8fd 100644 --- a/archives/2019/page/2/index.html +++ b/archives/2019/page/2/index.html @@ -822,14 +822,14 @@
    - +

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    diff --git a/archives/index.html b/archives/index.html index 41758eb01..03498c5a9 100644 --- a/archives/index.html +++ b/archives/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/2/index.html b/archives/page/2/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/2/index.html +++ b/archives/page/2/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/3/index.html b/archives/page/3/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/3/index.html +++ b/archives/page/3/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/4/index.html b/archives/page/4/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/4/index.html +++ b/archives/page/4/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/5/index.html b/archives/page/5/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/5/index.html +++ b/archives/page/5/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/6/index.html b/archives/page/6/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/6/index.html +++ b/archives/page/6/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/archives/page/7/index.html b/archives/page/7/index.html index 41758eb01..03498c5a9 100644 --- a/archives/page/7/index.html +++ b/archives/page/7/index.html @@ -651,7 +651,7 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS diff --git a/atom.xml b/atom.xml index 031b0da2d..78dce9c27 100644 --- a/atom.xml +++ b/atom.xml @@ -399,11 +399,11 @@ - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/ 2019-09-16T06:11:40.959Z - 2019-12-29T07:20:11.697Z + 2020-03-14T08:00:06.127Z

    部署到 Coding Pages 的好处:国内访问速度更快,可以提交百度收录(GitHub 禁止了百度的爬取)

    部署到 Coding Pages 的坏处:就今年来说,Coding 不太稳定,随时有宕机的可能,群里的朋友已经经历过几次了,不过相信以后会越来越稳定的

    部署过程中常见的问题:无法实现全站 HTTPS,Coding 申请 SSL 证书失败,浏览器可能会提示不是安全链接

    本文前提:你已经将 Hexo 成功部署到了 GitHub Pages,如果还没有,请参考:《使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】》

    本文将全面讲述如何成功双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS,同时解决一些常见的问题!


    1.创建项目

    进入 Coding 官网,点击个人版登陆,没有账号就注册一个并登录,由于 Coding 已经被腾讯收购了,所以登录就会来到腾讯云开发者平台,点击创建项目


    01

    项目名称建议和你的用户名一致,这样做的好处是:到时候可以直接通过 user_name.coding.me 访问你的博客,如果项目名与用户名不一致,则需要通过 user_name.coding.me/project_name 才能访问,项目描述可以随便写


    02

    2.配置公钥

    配置 SSH 公钥方法与 GitHub Pages 的方式差不多,点击你的头像,依次选择【个人设置】-【SSH公钥】-【新增公钥】


    03

    前面部署到 GitHub Pages 的时候就已经有了一对公钥,我们直接将该公钥粘贴进去就行,公钥名称可以随便写,选中永久有效选项

    PS:公钥储存位置一般在 C:\Users\用户名\.ssh 目录下的 id_rsa.pub 文件里,用记事本打开复制其内容即可


    04

    添加公钥后,我们可以右键 Get Bash,输入以下命令来检查是否配置成功:

    1
    ssh -T git@git.coding.net

    若出现以下提示,则证明配置成功:

    1
    2
    Coding 提示: Hello XXX, You've connected to Coding.net via SSH. This is a personal key.
    XXX,你好,你已经通过 SSH 协议认证 Coding.net 服务,这是一个个人公钥

    3.配置 _config.yml

    进入你的项目,在右下角有选择连接方式,选择 SSH 方式(HTTPS 方式也可以,但是这种方式有时候可能连接不上,SSH 连接不容易出问题),一键复制,然后打开你本地博客根目录的 _config.yml 文件,找到 deploy 关键字,添加 coding 地址:coding: git@git.dev.tencent.com:user_name/user_name.git,也就是刚刚复制的 SSH 地址


    05

    06

    添加完成后先执行命令 hexo clean 清理一下缓存,然后执行命令 hexo g -d 将博客双线部署到 Coding Pages 和 GitHub Pages,如下图所示表示部署成功:


    13

    4.开启 Coding Pages

    进入你的项目,在代码栏下选择 Pages 服务,一键开启 Coding Pages,等待几秒后刷新网页即可看到已经开启的 Coding Pages,到目前为止,你就可以通过 xxxx.coding.me(比如我的是 trhx.coding.me)访问你的 Coding Pages 页面了


    07

    08

    5.绑定域名并开启 HPPTS

    首先在你的域名 DNS 设置中添加一条 CNAME 记录指向 xxxx.coding.me,解析路线选择 默认,将 GitHub 的解析路线改为 境外,这样境外访问就会走 GitHub,境内就会走 Coding,也有人说阿里云是智能解析,自动分配路线,如果解析路线都是默认,境外访问同样会智能选择走 GitHub,境内走 Coding,我没有验证过,有兴趣的可以自己试试,我的解析如下图所示:


    09

    然后点击静态 Pages 应用右上角的设置,进入设置页面,这里要注意,如果你之前已经部署到了 GitHub Pages 并开启了 HTTPS,那么直接在设置页面绑定你自己的域名,SSL/TLS 安全证书就会显示申请错误,如下图所示,没有申请到 SSL 证书,当你访问你的网站时,浏览器就会提示不是安全连接


    10

    申请错误原因是:在验证域名所有权时会定位到 Github Pages 的主机上导致 SSL 证书申请失败

    正确的做法是:先去域名 DNS 把 GitHub 的解析暂停掉,然后再重新申请 SSL 证书,大约十秒左右就能申请成功,然后开启强制 HTTPS 访问

    这里也建议同时绑定有 www 前缀和没有 www 前缀的,如果要绑定没有 www 前缀的,首先要去域名 DNS 添加一个 A 记录,主机记录为 @,记录值为你博客 IP 地址,IP 地址可以在 cmd 命令行 ping 一下得到,然后在 Coding Pages 中设置其中一个为【首选】,另一个设置【跳转至首选】,这样不管用户是否输入 www 前缀都会跳到有 www 前缀的了

    在博客资源引用的时候也要注意所有资源的 URL 必须是以 https:// 开头,不然浏览器依旧会提示不安全!


    13

    11_1

    至此,我们的 Hexo 博客就成功双线部署到 Coding Pages 和 GitHub Pages 了,并且也实现了全站 HPPTS,最后来一张 GitHub Pages 和 Coding Pages 在国内的速度对比图,可以明显看到速度的提升


    12
    ]]>
    diff --git a/baidusitemap.xml b/baidusitemap.xml index c859db280..5451bfb7b 100644 --- a/baidusitemap.xml +++ b/baidusitemap.xml @@ -1,6 +1,51 @@ + https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/ + 2020-03-14 + + https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/ + 2020-03-14 + + https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/ + 2020-03-14 + + https://www.itrhx.com/2019/08/01/A27-image-hosting/ + 2020-03-14 + + https://www.itrhx.com/2019/07/31/A26-hexo-add-https/ + 2020-03-14 + + https://www.itrhx.com/2019/08/23/A25-SB/ + 2020-03-14 + + https://www.itrhx.com/2019/08/23/A23-beian/ + 2020-03-14 + + https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/ + 2020-03-14 + + https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/ + 2020-03-14 + + https://www.itrhx.com/2019/02/10/A18-free-cdn/ + 2020-03-14 + + https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/ + 2020-03-14 + + https://www.itrhx.com/2018/08/29/A05-markdown-editor/ + 2020-03-14 + + https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ + 2020-03-14 + + https://www.itrhx.com/2018/08/25/A03-markdown/ + 2020-03-14 + + https://www.itrhx.com/2018/08/15/A02-hexo-blog/ + 2020-03-14 + https://www.itrhx.com/2020/01/10/A61-build-a-SSR-server-with-VPS/ 2020-03-02 @@ -9,30 +54,12 @@ https://www.itrhx.com/2019/01/18/A16-deploy-two-or-more-hexo-blogs/ 2020-02-07 - - https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ - 2019-12-31 https://www.itrhx.com/2019/08/11/A28-hexo-add-https/ 2019-12-31 - - https://www.itrhx.com/2019/08/01/A27-image-hosting/ - 2019-12-31 - - https://www.itrhx.com/2019/08/23/A23-beian/ - 2019-12-31 https://www.itrhx.com/2018/09/13/A07-Python3-basic-C01/ 2019-12-29 - - https://www.itrhx.com/2018/08/15/A02-hexo-blog/ - 2019-12-29 - - https://www.itrhx.com/2019/07/31/A26-hexo-add-https/ - 2019-12-29 - - https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/ - 2019-12-29 https://www.itrhx.com/2019/09/17/A48-submit-search-engine-inclusion/ 2019-12-29 @@ -132,9 +159,6 @@ https://www.itrhx.com/2019/09/04/A41-Python3-spider-C11/ 2019-09-24 - - https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/ - 2019-09-24 https://www.itrhx.com/2019/08/26/A38-Python3-spider-C08/ 2019-09-24 @@ -147,9 +171,6 @@ https://www.itrhx.com/2019/08/23/A35-Python3-spider-C05/ 2019-09-24 - - https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/ - 2019-09-24 https://www.itrhx.com/2019/08/23/A29-Python3-spider-C01/ 2019-09-24 @@ -159,29 +180,8 @@ https://www.itrhx.com/2019/02/05/A17-happy-new-year/ 2019-09-09 - - https://www.itrhx.com/2019/08/23/A25-SB/ - 2019-09-09 - - https://www.itrhx.com/2019/02/10/A18-free-cdn/ - 2019-09-09 - - https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/ - 2019-09-09 - - https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/ - 2019-09-09 - - https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/ - 2019-09-09 https://www.itrhx.com/2019/08/23/A24-instant.page/ 2019-09-09 - - https://www.itrhx.com/2018/08/29/A05-markdown-editor/ - 2019-09-09 - - https://www.itrhx.com/2018/08/25/A03-markdown/ - 2019-09-09 diff --git a/categories/Hexo/index.html b/categories/Hexo/index.html index ad52f36ae..cdc27b94a 100644 --- a/categories/Hexo/index.html +++ b/categories/Hexo/index.html @@ -713,14 +713,14 @@
    - +

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    diff --git a/content.json b/content.json index c68d4f57d..4fb3ef737 100644 --- a/content.json +++ b/content.json @@ -1 +1 @@ -{"meta":{"title":"TRHX'S BLOG","subtitle":"一入 IT 深似海 从此学习无绝期","description":"TRHX 的个人博客;主攻 Python、爬虫、WEB前端、大数据、数据分析、数据可视化;求知若饥,虚心若愚,一入 IT 深似海,从此学习无绝期,记录毕生所学!","author":"TRHX","url":"https://www.itrhx.com"},"pages":[{"title":"","date":"2019-09-23T02:54:05.384Z","updated":"2019-09-23T02:54:05.384Z","comments":true,"path":"404.html","permalink":"https://www.itrhx.com/404.html","excerpt":"","text":"404 页面 | TRHX'S BLOG *{padding:0;margin:0}body{background-color:#eee;background-image:url(\"https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.9/images/background/021.webp\");}#catch-the-cat{width:100%;margin-top:32px;text-align:center}#Copyright{text-align:center;font-size:15px;color:#000;margin:30px}#Copyright a{color:#000}#Copyright a:hover{color:#f00} 404 Not Found! 很抱歉,您访问的页面不存在,可能是输入地址有误或该地址已被删除! window.game = new CatchTheCatGame({ w: 11, h: 11, r: 20, backgroundColor: 0xffffff, parent: 'catch-the-cat', statusBarAlign: 'center', credit: 'www.itrhx.com' }); Copyright ©2018-2019 TRHX'S BLOG       鄂ICP备19003281号-3     点击返回首页"},{"title":"","date":"2020-02-28T07:25:47.215Z","updated":"2020-02-28T07:25:47.215Z","comments":true,"path":"2019-nCoV/index.html","permalink":"https://www.itrhx.com/2019-nCoV/index.html","excerpt":"","text":"全国新型冠状病毒实时分布图 body { height: 88vh; margin: 0; padding: 0; overflow: hidden; } h2 { text-align:center; margin: 20px 0 0 0; color: #0822B5; } h3 { text-align:center; margin: 20px 0 0 0; color: #0822B5; } a { color: #178b50; text-decoration: none; } a:hover { color: #d81d1b; text-decoration: none; } iframe { overflow:hidden; margin: 20px 0 0 0; border: none; } TRHX'S BLOG丨新冠肺炎实时疫情图(数据来源:新浪新闻) 相关链接:武汉新型冠状病毒防疫信息收集平台 丨 2019-nCoV 疫情信息导航网站"},{"title":"","date":"2020-03-02T05:42:14.094Z","updated":"2020-03-02T05:42:14.094Z","comments":true,"path":"about/index.html","permalink":"https://www.itrhx.com/about/index.html","excerpt":"","text":"TRHX'S BLOG | ABOUT document.onkeydown = function () { if (window.event && window.event.keyCode == 123) { event.keyCode = 0; event.returnValue = false; return false; } }; 您的浏览器不支持audio标签,无法播放音乐! 江湖名称:TRHX 常驻之地:China~丨湖北丨武汉 初度之辰:1999 擅长领域:Python丨爬虫丨前端 技能丨Skill HTML/CSS/JS 65% C/C++ 20% JAVA 30% PYTHON 80% HEXO/GIT 90% PS/PR/AE 65% 简介丨Introduction ● 学历:武汉某本科,软件工程专业大三学生; ● 现况:自学 Python 中,网络爬虫方向; ● 目标:优秀前端工程师 or 网络爬虫工程师; ● 博客:好记性不如烂笔头,记录学习过程; ● 兴趣:酷爱编程,业余公路自行车手,WOT 玩家; ● 其他:虽然很菜,但是在努力学习中! 联系我丨Contact me Copyright © 2018-2020 TRHX'S BLOG. All rights reserved. if ('addEventListener' in window) { window.addEventListener('load', function () { document.body.className = document.body.className.replace(/\\bis-loading\\b/, ''); }); document.body.className += (navigator.userAgent.match(/(MSIE|rv:11\\.0)/) ? ' is-ie' : ''); } uniform mat4 uProjection; uniform mat4 uModelview; uniform vec3 uResolution; uniform vec3 uOffset; uniform vec3 uDOF; //x:focus distance, y:focus radius, z:max radius uniform vec3 uFade; //x:start distance, y:half distance, z:near fade start attribute vec3 aPosition; attribute vec3 aEuler; attribute vec2 aMisc; //x:size, y:fade varying vec3 pposition; varying float psize; varying float palpha; varying float pdist; //varying mat3 rotMat; varying vec3 normX; varying vec3 normY; varying vec3 normZ; varying vec3 normal; varying float diffuse; varying float specular; varying float rstop; varying float distancefade; void main(void) { // Projection is based on vertical angle vec4 pos = uModelview * vec4(aPosition + uOffset, 1.0); gl_Position = uProjection * pos; gl_PointSize = aMisc.x * uProjection[1][1] / -pos.z * uResolution.y * 0.5; pposition = pos.xyz; psize = aMisc.x; pdist = length(pos.xyz); palpha = smoothstep(0.0, 1.0, (pdist - 0.1) / uFade.z); vec3 elrsn = sin(aEuler); vec3 elrcs = cos(aEuler); mat3 rotx = mat3( 1.0, 0.0, 0.0, 0.0, elrcs.x, elrsn.x, 0.0, -elrsn.x, elrcs.x ); mat3 roty = mat3( elrcs.y, 0.0, -elrsn.y, 0.0, 1.0, 0.0, elrsn.y, 0.0, elrcs.y ); mat3 rotz = mat3( elrcs.z, elrsn.z, 0.0, -elrsn.z, elrcs.z, 0.0, 0.0, 0.0, 1.0 ); mat3 rotmat = rotx * roty * rotz; normal = rotmat[2]; mat3 trrotm = mat3( rotmat[0][0], rotmat[1][0], rotmat[2][0], rotmat[0][1], rotmat[1][1], rotmat[2][1], rotmat[0][2], rotmat[1][2], rotmat[2][2] ); normX = trrotm[0]; normY = trrotm[1]; normZ = trrotm[2]; const vec3 lit = vec3(0.6917144638660746, 0.6917144638660746, -0.20751433915982237); float tmpdfs = dot(lit, normal); if(tmpdfs < 0.0) { normal = -normal; tmpdfs = dot(lit, normal); } diffuse = 0.4 + tmpdfs; vec3 eyev = normalize(-pos.xyz); if(dot(eyev, normal) > 0.0) { vec3 hv = normalize(eyev + lit); specular = pow(max(dot(hv, normal), 0.0), 20.0); } else { specular = 0.0; } rstop = clamp((abs(pdist - uDOF.x) - uDOF.y) / uDOF.z, 0.0, 1.0); rstop = pow(rstop, 0.5); //-0.69315 = ln(0.5) distancefade = min(1.0, exp((uFade.x - pdist) * 0.69315 / uFade.y)); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform vec3 uDOF; //x:focus distance, y:focus radius, z:max radius uniform vec3 uFade; //x:start distance, y:half distance, z:near fade start const vec3 fadeCol = vec3(0.08, 0.03, 0.06); varying vec3 pposition; varying float psize; varying float palpha; varying float pdist; //varying mat3 rotMat; varying vec3 normX; varying vec3 normY; varying vec3 normZ; varying vec3 normal; varying float diffuse; varying float specular; varying float rstop; varying float distancefade; float ellipse(vec2 p, vec2 o, vec2 r) { vec2 lp = (p - o) / r; return length(lp) - 1.0; } void main(void) { vec3 p = vec3(gl_PointCoord - vec2(0.5, 0.5), 0.0) * 2.0; vec3 d = vec3(0.0, 0.0, -1.0); float nd = normZ.z; //dot(-normZ, d); if(abs(nd) < 0.0001) discard; float np = dot(normZ, p); vec3 tp = p + d * np / nd; vec2 coord = vec2(dot(normX, tp), dot(normY, tp)); //angle = 15 degree const float flwrsn = 0.258819045102521; const float flwrcs = 0.965925826289068; mat2 flwrm = mat2(flwrcs, -flwrsn, flwrsn, flwrcs); vec2 flwrp = vec2(abs(coord.x), coord.y) * flwrm; float r; if(flwrp.x < 0.0) { r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.36, 0.96) * 0.5); } else { r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.58, 0.96) * 0.5); } if(r > rstop) discard; vec3 col = mix(vec3(1.0, 0.8, 0.75), vec3(1.0, 0.9, 0.87), r); float grady = mix(0.0, 1.0, pow(coord.y * 0.5 + 0.5, 0.35)); col *= vec3(1.0, grady, grady); col *= mix(0.8, 1.0, pow(abs(coord.x), 0.3)); col = col * diffuse + specular; col = mix(fadeCol, col, distancefade); float alpha = (rstop > 0.001)? (0.5 - r / (rstop * 2.0)) : 1.0; alpha = smoothstep(0.0, 1.0, alpha) * palpha; gl_FragColor = vec4(col * 0.5, alpha); } uniform vec3 uResolution; attribute vec2 aPosition; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); texCoord = aPosition.xy * 0.5 + vec2(0.5, 0.5); screenCoord = aPosition.xy * vec2(uResolution.z, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform vec2 uTimes; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec3 col; float c; vec2 tmpv = texCoord * vec2(0.8, 1.0) - vec2(0.95, 1.0); c = exp(-pow(length(tmpv) * 1.8, 2.0)); col = mix(vec3(0.02, 0.0, 0.03), vec3(0.96, 0.98, 1.0) * 1.5, c); gl_FragColor = vec4(col * 0.5, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 col = texture2D(uSrc, texCoord); gl_FragColor = vec4(col.rgb * 2.0 - vec3(0.5), 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; uniform vec4 uBlurDir; //dir(x, y), stride(z, w) varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 col = texture2D(uSrc, texCoord); col = col + texture2D(uSrc, texCoord + uBlurDir.xy * uDelta); col = col + texture2D(uSrc, texCoord - uBlurDir.xy * uDelta); col = col + texture2D(uSrc, texCoord + (uBlurDir.xy + uBlurDir.zw) * uDelta); col = col + texture2D(uSrc, texCoord - (uBlurDir.xy + uBlurDir.zw) * uDelta); gl_FragColor = col / 5.0; } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_FragColor = texture2D(uSrc, texCoord); } uniform vec3 uResolution; attribute vec2 aPosition; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); texCoord = aPosition.xy * 0.5 + vec2(0.5, 0.5); screenCoord = aPosition.xy * vec2(uResolution.z, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform sampler2D uBloom; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 srccol = texture2D(uSrc, texCoord) * 2.0; vec4 bloomcol = texture2D(uBloom, texCoord); vec4 col; col = srccol + bloomcol * (vec4(1.0) + srccol); col *= smoothstep(1.0, 0.0, pow(length((texCoord - vec2(0.5)) * 2.0), 1.2) * 0.5); col = pow(col, vec4(0.45454545454545)); //(1.0 / 2.2) gl_FragColor = vec4(col.rgb, 1.0); gl_FragColor.a = 1.0; }"},{"title":"所有分类","date":"2019-04-27T16:28:11.064Z","updated":"2019-04-27T16:28:11.064Z","comments":true,"path":"categories/index.html","permalink":"https://www.itrhx.com/categories/index.html","excerpt":"","text":""},{"title":"留言","date":"2020-02-27T11:40:10.042Z","updated":"2020-02-27T11:40:10.042Z","comments":true,"path":"comments/index.html","permalink":"https://www.itrhx.com/comments/index.html","excerpt":"","text":"                                    采用 Gitalk 评论系统,需使用 GitHub 账号登录,请尽情灌水吧!😉由于 Gitalk 调用的是 GitHub 的 issues,如果您参与了评论,可能别人评论您也会收到邮件,若不想再收到邮件,可以到 issues 页面取消订阅,取消后,如果您再次评论,也不会再收到邮件提醒!"},{"title":"小伙伴们","date":"2020-03-04T14:05:45.065Z","updated":"2020-03-04T14:05:45.065Z","comments":false,"path":"friends/index.html","permalink":"https://www.itrhx.com/friends/index.html","excerpt":"","text":"名称:TRHX’S BLOG主页:https://www.itrhx.com/头像:https://www.itrhx.com/images/trhx.png标签:Python、爬虫、前端简介:求知若饥,虚心若愚! 由于目前友链数过多,所以暂停交换友链,敬请见谅!"},{"title":"所有标签","date":"2019-05-05T16:01:26.324Z","updated":"2019-05-05T16:01:26.324Z","comments":true,"path":"tags/index.html","permalink":"https://www.itrhx.com/tags/index.html","excerpt":"","text":""},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"box/about/index.html","permalink":"https://www.itrhx.com/box/about/index.html","excerpt":"","text":"关于本页丨TRHX'S BLOG 关于本页 本页面收集了比较常用或者实用的网站,相当于一个小小的导航页面。 整个页面由 Viggo 开发,完全开源,如果你也喜欢,欢迎去其 Github 点亮 star。 关于 Viggo Designer. Viggo. Full-time UI designer with an enduring interest in Coding. 一个全职的用户界面设计师,优秀的前端开发工程师,擅长 WEB 开发、WEB 设计、UI/UX 设计,对编程,拍照和单车有着持久的兴趣,生活在广州;如果您想招收此方面的人才,Viggo 无疑是一个很好的选择。 关于 TRHX TRHX 在校本科软件工程大三学生,主攻 Python、爬虫和前端。 一个想成为大佬的在校本科生,倾向于 Python、网络爬虫、数据分析、数据可视化、WEB 前端方面的学习,热爱编程、单车和户外运动,坚信好记性不如烂笔头,业精于勤荒于嬉,行成于思毁于随。 COPYRIGHT 2018 - 2020 WEBSTACK 丨 DESIGNED BY VIGGO 丨 CHANGED BY TRHX"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/2048/index.html","permalink":"https://www.itrhx.com/games/2048/index.html","excerpt":"","text":"2048 | TRHX'S BLOG 2048 使用方向键操作 New Game score:0 GAME OVER Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.753Z","updated":"2019-12-29T06:55:50.753Z","comments":true,"path":"games/PacMan/index.html","permalink":"https://www.itrhx.com/games/PacMan/index.html","excerpt":"","text":"吃豆人 | TRHX'S BLOG body{background-color: #000} *{padding:0;margin:0;} .wrapper{ width: 960px; margin:0 auto; line-height:36px; text-align:center; color:#999; } canvas{display:block;background: #000;} .mod-botton{ height: 32px; padding: 15px 0; text-align: center; } #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(255, 255, 255, 0.5);font-style:normal} #footer a{color:rgba(255, 255, 255, 0.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 不支持画布 【按空格键开始、暂停或继续游戏,方向键移动吃豆人】 Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/cat/index.html","permalink":"https://www.itrhx.com/games/cat/index.html","excerpt":"","text":"圈小猫 | TRHX'S BLOG body {background-color: #eeeeee} #catch-the-cat {width: 100%;text-align: center;} #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(0,0,0,.5);font-style:normal} #footer a{color:rgba(0,0,0,.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 游戏:《圈小猫》 window.game = new CatchTheCatGame({ w: 11, h: 11, r: 20, backgroundColor: 0xffffff, parent: 'catch-the-cat', statusBarAlign: 'center', credit: 'www.itrhx.com' }); Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/element/index.html","permalink":"https://www.itrhx.com/games/element/index.html","excerpt":"","text":"3D元素周期表 | TRHX'S BLOG html, body { height: 100%; } body { background-color: #000000; margin: 0; font-family: Helvetica, sans-serif;; overflow: hidden; } a { color: #ffffff; } #info { position: absolute; width: 100%; color: #ffffff; padding: 5px; font-family: Monospace; font-size: 13px; font-weight: bold; text-align: center; z-index: 1; } #menu { position: absolute; bottom: 20px; width: 100%; text-align: center; font-family: verdana,Tahoma,Arial,Hei,\"Microsoft Yahei\",SimHei; } .element { width: 120px; height: 160px; box-shadow: 0px 0px 12px rgba(0,255,255,0.5); border: 1px solid rgba(127,255,255,0.25); text-align: center; cursor: default; } .element:hover { box-shadow: 0px 0px 12px rgba(0,255,255,0.75); border: 1px solid rgba(127,255,255,0.75); } .element .number { position: absolute; top: 20px; right: 20px; font-size: 12px; color: rgba(127,255,255,0.75); } .element .symbol { position: absolute; top: 40px; left: 0px; right: 0px; font-size: 60px; font-weight: bold; color: rgba(255,255,255,0.75); text-shadow: 0 0 10px rgba(0,255,255,0.95); } .element .details { position: absolute; bottom: 15px; left: 0px; right: 0px; font-size: 12px; color: rgba(127,255,255,0.75); } button { color: rgba(127,255,255,0.75); background: transparent; outline: 1px solid rgba(127,255,255,0.75); border: 0px; padding: 5px 10px; cursor: pointer; } button:hover { background-color: rgba(0,255,255,0.5); } button:active { color: #000000; background-color: rgba(0,255,255,0.75); } 表面 球体 螺旋 网格 var table = [ \"H\", \"Hydrogen\", \"1.00794\", 1, 1, \"He\", \"Helium\", \"4.002602\", 18, 1, \"Li\", \"Lithium\", \"#6.941\", 1, 2, \"Be\", \"Beryllium\", \"9.012182\", 2, 2, \"B\", \"Boron\", \"#10.811\", 13, 2, \"C\", \"Carbon\", \"#12.0107\", 14, 2, \"N\", \"Nitrogen\", \"#14.0067\", 15, 2, \"O\", \"Oxygen\", \"#15.9994\", 16, 2, \"F\", \"Fluorine\", \"18.9984032\", 17, 2, \"Ne\", \"Neon\", \"#20.1797\", 18, 2, \"Na\", \"Sodium\", \"22.98976...\", 1, 3, \"Mg\", \"Magnesium\", \"#24.305\", 2, 3, \"Al\", \"Aluminium\", \"26.9815386\", 13, 3, \"Si\", \"Silicon\", \"#28.0855\", 14, 3, \"P\", \"Phosphorus\", \"30.973762\", 15, 3, \"S\", \"Sulfur\", \"#32.065\", 16, 3, \"Cl\", \"Chlorine\", \"#35.453\", 17, 3, \"Ar\", \"Argon\", \"#39.948\", 18, 3, \"K\", \"Potassium\", \"#39.948\", 1, 4, \"Ca\", \"Calcium\", \"#40.078\", 2, 4, \"Sc\", \"Scandium\", \"44.955912\", 3, 4, \"Ti\", \"Titanium\", \"#47.867\", 4, 4, \"V\", \"Vanadium\", \"#50.9415\", 5, 4, \"Cr\", \"Chromium\", \"#51.9961\", 6, 4, \"Mn\", \"Manganese\", \"54.938045\", 7, 4, \"Fe\", \"Iron\", \"#55.845\", 8, 4, \"Co\", \"Cobalt\", \"58.933195\", 9, 4, \"Ni\", \"Nickel\", \"#58.6934\", 10, 4, \"Cu\", \"Copper\", \"#63.546\", 11, 4, \"Zn\", \"Zinc\", \"#65.38\", 12, 4, \"Ga\", \"Gallium\", \"#69.723\", 13, 4, \"Ge\", \"Germanium\", \"#72.63\", 14, 4, \"As\", \"Arsenic\", \"#74.9216\", 15, 4, \"Se\", \"Selenium\", \"#78.96\", 16, 4, \"Br\", \"Bromine\", \"#79.904\", 17, 4, \"Kr\", \"Krypton\", \"#83.798\", 18, 4, \"Rb\", \"Rubidium\", \"#85.4678\", 1, 5, \"Sr\", \"Strontium\", \"#87.62\", 2, 5, \"Y\", \"Yttrium\", \"88.90585\", 3, 5, \"Zr\", \"Zirconium\", \"#91.224\", 4, 5, \"Nb\", \"Niobium\", \"92.90628\", 5, 5, \"Mo\", \"Molybdenum\", \"#95.96\", 6, 5, \"Tc\", \"Technetium\", \"(98)\", 7, 5, \"Ru\", \"Ruthenium\", \"#101.07\", 8, 5, \"Rh\", \"Rhodium\", \"#102.9055\", 9, 5, \"Pd\", \"Palladium\", \"#106.42\", 10, 5, \"Ag\", \"Silver\", \"#107.8682\", 11, 5, \"Cd\", \"Cadmium\", \"#112.411\", 12, 5, \"In\", \"Indium\", \"#114.818\", 13, 5, \"Sn\", \"Tin\", \"#118.71\", 14, 5, \"Sb\", \"Antimony\", \"#121.76\", 15, 5, \"Te\", \"Tellurium\", \"127.6\", 16, 5, \"I\", \"Iodine\", \"126.90447\", 17, 5, \"Xe\", \"Xenon\", \"#131.293\", 18, 5, \"Cs\", \"Caesium\", \"#132.9054\", 1, 6, \"Ba\", \"Barium\", \"#132.9054\", 2, 6, \"La\", \"Lanthanum\", \"138.90547\", 4, 9, \"Ce\", \"Cerium\", \"#140.116\", 5, 9, \"Pr\", \"Praseodymium\", \"140.90765\", 6, 9, \"Nd\", \"Neodymium\", \"#144.242\", 7, 9, \"Pm\", \"Promethium\", \"(145)\", 8, 9, \"Sm\", \"Samarium\", \"#150.36\", 9, 9, \"Eu\", \"Europium\", \"#151.964\", 10, 9, \"Gd\", \"Gadolinium\", \"#157.25\", 11, 9, \"Tb\", \"Terbium\", \"158.92535\", 12, 9, \"Dy\", \"Dysprosium\", \"162.5\", 13, 9, \"Ho\", \"Holmium\", \"164.93032\", 14, 9, \"Er\", \"Erbium\", \"#167.259\", 15, 9, \"Tm\", \"Thulium\", \"168.93421\", 16, 9, \"Yb\", \"Ytterbium\", \"#173.054\", 17, 9, \"Lu\", \"Lutetium\", \"#174.9668\", 18, 9, \"Hf\", \"Hafnium\", \"#178.49\", 4, 6, \"Ta\", \"Tantalum\", \"180.94788\", 5, 6, \"W\", \"Tungsten\", \"#183.84\", 6, 6, \"Re\", \"Rhenium\", \"#186.207\", 7, 6, \"Os\", \"Osmium\", \"#190.23\", 8, 6, \"Ir\", \"Iridium\", \"#192.217\", 9, 6, \"Pt\", \"Platinum\", \"#195.084\", 10, 6, \"Au\", \"Gold\", \"196.966569\", 11, 6, \"Hg\", \"Mercury\", \"#200.59\", 12, 6, \"Tl\", \"Thallium\", \"#204.3833\", 13, 6, \"Pb\", \"Lead\", \"207.2\", 14, 6, \"Bi\", \"Bismuth\", \"#208.9804\", 15, 6, \"Po\", \"Polonium\", \"(209)\", 16, 6, \"At\", \"Astatine\", \"(210)\", 17, 6, \"Rn\", \"Radon\", \"(222)\", 18, 6, \"Fr\", \"Francium\", \"(223)\", 1, 7, \"Ra\", \"Radium\", \"(226)\", 2, 7, \"Ac\", \"Actinium\", \"(227)\", 4, 10, \"Th\", \"Thorium\", \"232.03806\", 5, 10, \"Pa\", \"Protactinium\", \"#231.0588\", 6, 10, \"U\", \"Uranium\", \"238.02891\", 7, 10, \"Np\", \"Neptunium\", \"(237)\", 8, 10, \"Pu\", \"Plutonium\", \"(244)\", 9, 10, \"Am\", \"Americium\", \"(243)\", 10, 10, \"Cm\", \"Curium\", \"(247)\", 11, 10, \"Bk\", \"Berkelium\", \"(247)\", 12, 10, \"Cf\", \"Californium\", \"(251)\", 13, 10, \"Es\", \"Einstenium\", \"(252)\", 14, 10, \"Fm\", \"Fermium\", \"(257)\", 15, 10, \"Md\", \"Mendelevium\", \"(258)\", 16, 10, \"No\", \"Nobelium\", \"(259)\", 17, 10, \"Lr\", \"Lawrencium\", \"(262)\", 18, 10, \"Rf\", \"Rutherfordium\", \"(267)\", 4, 7, \"Db\", \"Dubnium\", \"(268)\", 5, 7, \"Sg\", \"Seaborgium\", \"(271)\", 6, 7, \"Bh\", \"Bohrium\", \"(272)\", 7, 7, \"Hs\", \"Hassium\", \"(270)\", 8, 7, \"Mt\", \"Meitnerium\", \"(276)\", 9, 7, \"Ds\", \"Darmstadium\", \"(281)\", 10, 7, \"Rg\", \"Roentgenium\", \"(280)\", 11, 7, \"Cn\", \"Copernicium\", \"(285)\", 12, 7, \"Uut\", \"Unutrium\", \"(284)\", 13, 7, \"Fl\", \"Flerovium\", \"(289)\", 14, 7, \"Uup\", \"Ununpentium\", \"(288)\", 15, 7, \"Lv\", \"Livermorium\", \"(293)\", 16, 7, \"Uus\", \"Ununseptium\", \"(294)\", 17, 7, \"Uuo\", \"Ununoctium\", \"(294)\", 18, 7 ]; var camera, scene, renderer; var controls; var objects = []; var targets = { table: [], sphere: [], helix: [], grid: [] }; init(); animate(); function init() { camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.z = 3000; scene = new THREE.Scene(); // table for ( var i = 0; i < table.length; i += 5 ) { var element = document.createElement( 'div' ); element.className = 'element'; element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')'; var number = document.createElement( 'div' ); number.className = 'number'; number.textContent = (i/5) + 1; element.appendChild( number ); var symbol = document.createElement( 'div' ); symbol.className = 'symbol'; symbol.textContent = table[ i ]; element.appendChild( symbol ); var details = document.createElement( 'div' ); details.className = 'details'; details.innerHTML = table[ i + 1 ] + '' + table[ i + 2 ]; element.appendChild( details ); var object = new THREE.CSS3DObject( element ); object.position.x = Math.random() * 4000 - 2000; object.position.y = Math.random() * 4000 - 2000; object.position.z = Math.random() * 4000 - 2000; scene.add( object ); objects.push( object ); // var object = new THREE.Object3D(); object.position.x = ( table[ i + 3 ] * 140 ) - 1330; object.position.y = - ( table[ i + 4 ] * 180 ) + 990; targets.table.push( object ); } // sphere var vector = new THREE.Vector3(); for ( var i = 0, l = objects.length; i < l; i ++ ) { var phi = Math.acos( -1 + ( 2 * i ) / l ); var theta = Math.sqrt( l * Math.PI ) * phi; var object = new THREE.Object3D(); object.position.x = 800 * Math.cos( theta ) * Math.sin( phi ); object.position.y = 800 * Math.sin( theta ) * Math.sin( phi ); object.position.z = 800 * Math.cos( phi ); vector.copy( object.position ).multiplyScalar( 2 ); object.lookAt( vector ); targets.sphere.push( object ); } // helix var vector = new THREE.Vector3(); for ( var i = 0, l = objects.length; i < l; i ++ ) { var phi = i * 0.175 + Math.PI; var object = new THREE.Object3D(); object.position.x = 900 * Math.sin( phi ); object.position.y = - ( i * 8 ) + 450; object.position.z = 900 * Math.cos( phi ); vector.x = object.position.x * 2; vector.y = object.position.y; vector.z = object.position.z * 2; object.lookAt( vector ); targets.helix.push( object ); } // grid for ( var i = 0; i < objects.length; i ++ ) { var object = new THREE.Object3D(); object.position.x = ( ( i % 5 ) * 400 ) - 800; object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800; object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000; targets.grid.push( object ); } // renderer = new THREE.CSS3DRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.domElement.style.position = 'absolute'; document.getElementById( 'container' ).appendChild( renderer.domElement ); // controls = new THREE.TrackballControls( camera, renderer.domElement ); controls.rotateSpeed = 0.5; controls.minDistance = 500; controls.maxDistance = 6000; controls.addEventListener( 'change', render ); var button = document.getElementById( 'table' ); button.addEventListener( 'click', function ( event ) { transform( targets.table, 2000 ); }, false ); var button = document.getElementById( 'sphere' ); button.addEventListener( 'click', function ( event ) { transform( targets.sphere, 2000 ); }, false ); var button = document.getElementById( 'helix' ); button.addEventListener( 'click', function ( event ) { transform( targets.helix, 2000 ); }, false ); var button = document.getElementById( 'grid' ); button.addEventListener( 'click', function ( event ) { transform( targets.grid, 2000 ); }, false ); transform( targets.table, 5000 ); // window.addEventListener( 'resize', onWindowResize, false ); } function transform( targets, duration ) { TWEEN.removeAll(); for ( var i = 0; i < objects.length; i ++ ) { var object = objects[ i ]; var target = targets[ i ]; new TWEEN.Tween( object.position ) .to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration ) .easing( TWEEN.Easing.Exponential.InOut ) .start(); new TWEEN.Tween( object.rotation ) .to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration ) .easing( TWEEN.Easing.Exponential.InOut ) .start(); } new TWEEN.Tween( this ) .to( {}, duration * 2 ) .onUpdate( render ) .start(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); render(); } function animate() { requestAnimationFrame( animate ); TWEEN.update(); controls.update(); } function render() { renderer.render( scene, camera ); } document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.754Z","updated":"2019-12-29T06:55:50.754Z","comments":true,"path":"games/gobang/index.html","permalink":"https://www.itrhx.com/games/gobang/index.html","excerpt":"","text":"五子棋 | TRHX'S BLOG canvas { display: block; margin: 60px auto; box-shadow: -2px -2px 2px #efefef, 5px 5px 5px #b9b9b9; cursor: pointer; } .btn-wrap { display: flex; flex-direction: row; justify-content: center; } .btn-wrap div { margin: 0 10px; } div>span { display: inline-block; padding: 10px 20px; color: #fff; background-color: #6496ED; border-radius: 5px; cursor: pointer; } div.unable span { background: #D6D6D4; color: #adacaa; } #result-wrap { text-align: center; margin:50px 0 0 0; } #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(0,0,0,.5);font-style:normal} #footer a{color:rgba(0,0,0,.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 人机五子棋对弈 重新开始 悔棋 撤销悔棋 var over = false; var me = true; //我 var _nowi = 0, _nowj = 0; //记录自己下棋的坐标 var _compi = 0, _compj = 0; //记录计算机当前下棋的坐标 var _myWin = [], _compWin = []; //记录我,计算机赢的情况 var backAble = false, returnAble = false; var resultTxt = document.getElementById('result-wrap'); var chressBord = []; //棋盘 for (var i = 0; i < 15; i++) { chressBord[i] = []; for (var j = 0; j < 15; j++) { chressBord[i][j] = 0; } } //赢法的统计数组 var myWin = []; var computerWin = []; //赢法数组 var wins = []; for (var i = 0; i < 15; i++) { wins[i] = []; for (var j = 0; j < 15; j++) { wins[i][j] = []; } } var count = 0; //赢法总数 //横线赢法 for (var i = 0; i < 15; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[i][j + k][count] = true; } count++; } } //竖线赢法 for (var i = 0; i < 15; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[j + k][i][count] = true; } count++; } } //正斜线赢法 for (var i = 0; i < 11; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[i + k][j + k][count] = true; } count++; } } //反斜线赢法 for (var i = 0; i < 11; i++) { for (var j = 14; j > 3; j--) { for (var k = 0; k < 5; k++) { wins[i + k][j - k][count] = true; } count++; } } // debugger; for (var i = 0; i < count; i++) { myWin[i] = 0; _myWin[i] = 0; computerWin[i] = 0; _compWin[i] = 0; } var chess = document.getElementById(\"chess\"); var context = chess.getContext('2d'); context.strokeStyle = '#bfbfbf'; //边框颜色 var backbtn = document.getElementById(\"goback\"); var returnbtn = document.getElementById(\"return\"); window.onload = function () { drawChessBoard(); // 画棋盘 } document.getElementById(\"restart\").onclick = function () { window.location.reload(); } // 我,下棋 chess.onclick = function (e) { if (over) { return; } if (!me) { return; } // 悔棋功能可用 backbtn.className = backbtn.className.replace(new RegExp(\"(\\\\s|^)unable(\\\\s|$)\"), \" \"); var x = e.offsetX; var y = e.offsetY; var i = Math.floor(x / 30); var j = Math.floor(y / 30); _nowi = i; _nowj = j; if (chressBord[i][j] == 0) { oneStep(i, j, me); chressBord[i][j] = 1; //我,已占位置 for (var k = 0; k < count; k++) { // 将可能赢的情况都加1 if (wins[i][j][k]) { // debugger; myWin[k]++; _compWin[k] = computerWin[k]; computerWin[k] = 6; //这个位置对方不可能赢了 if (myWin[k] == 5) { // window.alert('你赢了'); resultTxt.innerHTML = '恭喜,你赢了!'; over = true; } } } if (!over) { me = !me; computerAI(); } } } // 悔棋 backbtn.onclick = function (e) { if (!backAble) { return; } over = false; me = true; // resultTxt.innerHTML = 'emmmm,悔棋中'; // 撤销悔棋功能可用 returnbtn.className = returnbtn.className.replace(new RegExp(\"(\\\\s|^)unable(\\\\s|$)\"), \" \"); // 我,悔棋 chressBord[_nowi][_nowj] = 0; //我,已占位置 还原 minusStep(_nowi, _nowj); //销毁棋子 for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_nowi][_nowj][k]) { myWin[k]--; computerWin[k] = _compWin[k]; //这个位置对方可能赢 } }// 计算机相应的悔棋 chressBord[_compi][_compj] = 0; //计算机,已占位置 还原 minusStep(_compi, _compj); //销毁棋子 for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_compi][_compj][k]) { computerWin[k]--; myWin[k] = _myWin[i]; //这个位置对方可能赢 } } resultTxt.innerHTML = '--人机五子棋--'; returnAble = true; backAble = false; } // 撤销悔棋 returnbtn.onclick = function (e) { if (!returnAble) { return; } // 我,撤销悔棋 chressBord[_nowi][_nowj] = 1; //我,已占位置 oneStep(_nowi, _nowj, me); for (var k = 0; k < count; k++) { if (wins[_nowi][_nowj][k]) { myWin[k]++; _compWin[k] = computerWin[k]; computerWin[k] = 6; //这个位置对方不可能赢 } if (myWin[k] == 5) { resultTxt.innerHTML = '恭喜,你赢了!'; over = true; } }// 计算机撤销相应的悔棋 chressBord[_compi][_compj] = 2; //计算机,已占位置 oneStep(_compi, _compj, false); for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_compi][_compj][k]) { computerWin[k]++; _myWin[k] = myWin[k]; myWin[k] = 6; //这个位置对方不可能赢 } if (computerWin[k] == 5) { resultTxt.innerHTML = '很遗憾,计算机赢了,继续加油哦!'; over = true; } } returnbtn.className += ' ' + 'unable'; returnAble = false; backAble = true; } // 计算机下棋 var computerAI = function () { var myScore = []; var computerScore = []; var max = 0; var u = 0, v = 0; for (var i = 0; i < 15; i++) { myScore[i] = []; computerScore[i] = []; for (var j = 0; j < 15; j++) { myScore[i][j] = 0; computerScore[i][j] = 0; } } for (var i = 0; i < 15; i++) { for (var j = 0; j < 15; j++) { if (chressBord[i][j] == 0) { for (var k = 0; k < count; k++) { if (wins[i][j][k]) { if (myWin[k] == 1) { myScore[i][j] += 200; } else if (myWin[k] == 2) { myScore[i][j] += 400; } else if (myWin[k] == 3) { myScore[i][j] += 2000; } else if (myWin[k] == 4) { myScore[i][j] += 10000; } if (computerWin[k] == 1) { computerScore[i][j] += 220; } else if (computerWin[k] == 2) { computerScore[i][j] += 420; } else if (computerWin[k] == 3) { computerScore[i][j] += 2100; } else if (computerWin[k] == 4) { computerScore[i][j] += 20000; } } } if (myScore[i][j] > max) { max = myScore[i][j]; u = i; v = j; } else if (myScore[i][j] == max) { if (computerScore[i][j] > computerScore[u][v]) { u = i; v = j; } } if (computerScore[i][j] > max) { max = computerScore[i][j]; u = i; v = j; } else if (computerScore[i][j] == max) { if (myScore[i][j] > myScore[u][v]) { u = i; v = j; } } } } } _compi = u; _compj = v; oneStep(u, v, false); chressBord[u][v] = 2;//计算机占据位置 for (var k = 0; k < count; k++) { if (wins[u][v][k]) { computerWin[k]++; _myWin[k] = myWin[k]; myWin[k] = 6; //这个位置对方不可能赢了 if (computerWin[k] == 5) { resultTxt.innerHTML = '很遗憾,计算机赢了,继续加油哦!'; over = true; } } } if (!over) { me = !me; } backAble = true; returnAble = false; var hasClass = new RegExp('unable').test(' ' + returnbtn.className + ' '); if (!hasClass) { returnbtn.className += ' ' + 'unable'; } } //绘画棋盘 var drawChessBoard = function () { for (var i = 0; i < 15; i++) { context.moveTo(15 + i * 30, 15); context.lineTo(15 + i * 30, 435); context.stroke(); context.moveTo(15, 15 + i * 30); context.lineTo(435, 15 + i * 30); context.stroke(); } } //画棋子 var oneStep = function (i, j, me) { context.beginPath(); context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI); // 画圆 context.closePath(); //渐变 var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0); if (me) { gradient.addColorStop(0, '#0a0a0a'); gradient.addColorStop(1, '#636766'); } else { gradient.addColorStop(0, '#d1d1d1'); gradient.addColorStop(1, '#f9f9f9'); } context.fillStyle = gradient; context.fill(); } //销毁棋子 var minusStep = function (i, j) { //擦除该圆 context.clearRect((i) * 30, (j) * 30, 30, 30); // 重画该圆周围的格子 context.beginPath(); context.moveTo(15 + i * 30, j * 30); context.lineTo(15 + i * 30, j * 30 + 30); context.moveTo(i * 30, j * 30 + 15); context.lineTo((i + 1) * 30, j * 30 + 15); context.stroke(); } Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.754Z","updated":"2019-12-29T06:55:50.754Z","comments":true,"path":"games/piano/index.html","permalink":"https://www.itrhx.com/games/piano/index.html","excerpt":"","text":"网页版钢琴 | TRHX'S BLOG 网页版钢琴 qaz sx dc rfv gb hn jm ik, w e t y u 弹奏方法 使用鼠标左键点击钢琴键,或者键入钢琴键上输入的键盘字母。 Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2020-02-27T12:02:54.700Z","updated":"2020-02-27T12:02:54.700Z","comments":true,"path":"box/index.html","permalink":"https://www.itrhx.com/box/index.html","excerpt":"","text":"百宝箱丨TRHX'S BLOG 开发社区 代码托管 语言文档 技能训练 在线平台 高校平台 游戏编程 HOT Pythoner 文档资料 博客收藏 学习资源 组织社区 爬虫相关 HOT 学习教程 在线视频 博客论坛 学习平台 常用工具 站长工具 HOT IT工具箱 文件处理 HOT 设计素材 效率软件 HOT 服务平台 云服务商 众包平台 站内游戏 HOT 更多导航 关于本页 隐藏/显示侧边栏 博客首页 友情链接 评论留言 关于博主 (function(a,h,g,f,e,d,c,b){b=function(){d=h.createElement(g);c=h.getElementsByTagName(g)[0];d.src=e;d.charset=\"utf-8\";d.async=1;c.parentNode.insertBefore(d,c)};a[\"SeniverseWeatherWidgetObject\"]=f;a[f]||(a[f]=function(){(a[f].q=a[f].q||[]).push(arguments)});a[f].l=+new Date();if(a.attachEvent){a.attachEvent(\"onload\",b)}else{a.addEventListener(\"load\",b,false)}}(window,document,\"script\",\"SeniverseWeatherWidget\",\"//cdn.sencdn.com/widget2/static/js/bundle.js?t=\"+parseInt((new Date().getTime() / 100000000).toString(),10))); window.SeniverseWeatherWidget('show', { flavor: \"slim\", location: \"WX4FBXXFKE4F\", geolocation: true, language: \"auto\", unit: \"c\", theme: \"auto\", token: \"a39cd5a0-4024-4cb2-85c6-0250317058db\", hover: \"enabled\", container: \"tp-weather-widget\" }) 开发社区 Stack Overflow 全球最受程序员欢迎的开发社区 CSDN 全球最大中文IT社区,为IT专业技术人员提供最全面的信息传播和服务平台 博客园 代码改变世界 V2EX V2EX = way to explore 掘金 一个帮助开发者成长的社区 SegmentFault 改变并提升人们获取知识的方式和效率,帮助更多的开发者获得成长与成功 开源中国 国内最大的开源技术社区 ITeye ITeye软件开发交流社区 - Java编程 Spring框架 Ajax技术 agile敏捷软件开发 ruby on rails实践 51CTO 技术成就梦想 ITPUB 全球最大的学习分享平台 知乎 国内最受欢迎的知识性问答社区 简书 创作你的创作 云+社区 来自腾讯的开发者技术分享社区 云栖社区 阿里云面向开发者的开放型技术平台 代码托管 Github 全球最大的面向开源及私有软件项目的托管平台 Gitlab 支持无限的公有项目和私有项目的代码托管平台 Bitbucket 同时支持 Git 和 Mercurial 这两个版本控制软件,免费的私有仓库,支持5人以内的合作开发 SourceForge 又称 SF.net,是开源软件开发者进行开发管理的集中式场所 Coding 国内首个一站式云端软件服务平台 Gitee 国内最大的开源社区 OSChina 的代码托管平台 阿里云代码托管 阿里云旗下代码托管平台 百度效率云 百度云旗下的 Git 代码托管平台 语言文档 Zeal 脱机文档浏览器,包含196种语言API文档,支持Windows、Linux和macOS Dash 适用于Mac OS平台的软件编程文档管理工具,可以浏览API文档,以及管理代码片段工具。自带了丰富的API文档,涉及各种主流的编程语言和框架 DevDocs 在快速,有条理和可搜索的界面中结合了多个API文档,可以在移动设备上离线运行,并且可以安装在Chrome上 C/C++ C/C++ API 文档 C# C# API 文档 Java Java API 文档 .NET .NET API 文档 PHP PHP API 文档 JavaScript JavaScript API 文档 Python Python API 文档 Android Android API 文档 iOS iOS API 文档 SQL SQL API 文档 Swift Swift API 文档 Ruby Ruby API 文档 GO GO API 文档 R R API 文档 MATLAB MATLAB API 文档 Node.js Node.js API 文档 HTML HTML API 文档 CSS CSS API 文档 Redis Redis API 文档 MongoDB MongoDB API 文档 Django Django API 文档 在线平台 LeetCode 全球极客挚爱的技术成长平台 Topcoder 全世界规模最大的程序竞赛网站,也会有一些算法竞赛,适合一些高端的或者搞ACM的,也会举办一些比赛 Codeforces 俄罗斯最大的算法比赛网站 Hihocoder 技术团队来自原北京大学POJ (PKU Online Judge)开发团队,收集了全球范围内很多地区、高校举办的比赛试题, 提供365天*24小时的在线提交、评判程序的服务 LintCode 被称作中文版的leetcode,也是可以做为编程能力提升的一个中转站 SPOJ 波兰的算法刷题网站 NEUQ OJ 一个在线的判题平台 洛谷 创办于2013年,致力于为参加noip、noi、acm的选手提供清爽、快捷的编程体验 牛客网 中国最大的IT题库 C语言网 在这里可以参加包括ACM、NOI在内的各种C/C++/java程序比赛,也可以DIY举办各类程序比赛活动! 计蒜客 计蒜客OI题库致力于为参加noi、noip、信息学竞赛的选手提供优秀的Online Judge系统 高校平台 POJ 北京大学程序在线评测系统 FDU OJ 复旦大学程序在线评测系统 TJ OJ 同济大学程序在线评测系统 USTC OJ 中国科学技术大学程序在线评测系统 ZOJ 浙江大学程序在线评测系统 HDU OJ 杭州电子科技大学程序在线评测系统 CSU-ACM 中南大学程序在线评测系统 HOJX 哈尔滨工业大学程序在线评测系统 HRBUST OJ 哈尔滨理工大学程序在线评测系统 PowerOJ 西南科技大学程序在线评测系统 SCU OJ 四川大学程序在线评测系统 FZU CoidngOJ 福州大学程序在线评测系统 NBUT OJ 宁波工程学院程序在线评测系统 Lutece 电子科技大学程序在线评测系统 武汉大学 ACM 协会 武汉大学 ACM 协会 ZJUT OJ 浙江工业大学程序在线评测系统 游戏编程 CheckiO 面向初学者和高级程序员的编码游戏,使用Python和JavaScript解决棘手的挑战和有趣的任务,从而提高您的编码技能 Coding Games 支持包括PHP、C、JavaScript在内的20多种编程语言。用户界面功能强大,可以定制 Codewars 一个外国的在线练习编程的网站,做题的过程类似打怪,做题升级,而且可以看到别人的解法,里面有很多巧妙的写法可以学习 CodeCombat 一个面向学生的游戏和CS学习平台。这是一个社区项目,有数百玩家自愿提供支持。支持语言包括Java、JS、Python、Lua、CoffeeScript Screeps 在游戏中学习JavaScript。世界上第一款针对程序员的MMO沙盒游戏 VIM Adventures 玩游戏的时候学VIM Cyber-Dojo 一个提供给程序员们练习写程序的地方。支持语言包括JavaScript、Java、Python、PHP、Ruby和很多其他语言 Elevator Saga 电梯编程游戏,跟随关卡解决所有挑站,使用语言为JavaScript Ruby Quiz 一个Ruby程序员提供的每周编程挑战项目 hacker.org 这项挑战由一系列本设计来强化你黑客技巧的解密、诡计、测试、烧脑环节组成。想要通关本系列,你必须学会解密、编码、渗透 Ruby Warrior 玩游戏学Ruby,通过Ruby脚本来控制一个Warrior通过每一关,每一关的代码难度都会有所增加,使玩家逐渐了解Ruby基本的函数、控制、变量、数组等语言特性的用法 文档资料 Python 官方文档 Python 官方文档 Python 标准库 Python 标准库 Python Requests Python Requests 文档 Python Urllib Python Urllib 文档 Python Selenium Python Selenium 中文翻译文档 正则表达式 Python 正则表达式官方文档 Beautiful Soup Beautiful Soup 文档 Scrapy Scrapy 爬虫框架官方文档 PySpider PySpider 爬虫框架官方文档 Matplotlib Matplotlib 2D绘图库 官方中文文档 Numpy Numpy 科学计算 官方中文文档 Pandas Pandas 结构化数据分析 官方中文文档 博客收藏 廖雪峰 廖雪峰的官方网站 - 研究互联网产品和技术,提供原创中文精品教程 崔庆才 崔庆才的个人博客,专注PHP,Python,爬虫,深度学习,机器学习,数据分析 莫烦Python 专注Python、机器学习、深度学习 唐松 专注Python网络爬虫, 数据科学, 数据挖掘, 数据分析 捕蛇者说 编程、程序员、Python FxxkPython 学习python的正确姿势 wistbean Python 大佬 Piglei Python 大佬 TendCode Python 大佬 追梦人物的博客 Python Django 大佬 the5fire 《Django企业开发实战》作者,关注Python、Django、Vim、Linux、Web开发 小明明S À DOMICILE 《Python Web开发实战》作者,Python 大佬 Python之禅 Python 大佬 Python 知识圈 Python知识圈 - 实用的Python教程网站 Python 教程网 小詹学Python,专注Python学习 烂笔头 j_hao104 Python大佬 咸鱼日常 专注Python爬虫,有许多JS逆向文章 AnSheng Python 全栈大佬 夏溪辰 云栖社区特邀爬虫工程师,Python大佬 高级农民工 Python大佬 云爬虫技术研究笔记 Lateautumn4lin 爬虫开发工程师,多年反爬虫破解经验,沉迷数据分析和黑客增长,CSDN博客专家,华为云享专家 云爬虫技术研究笔记(CSDN) Lateautumn4lin 爬虫开发工程师,多年反爬虫破解经验,沉迷数据分析和黑客增长,CSDN博客专家,华为云享专家 Jack Cui CSDN博客专家,Python 大佬 学习资源 Python爬虫人工智能学习教程 Python爬虫人工智能学习教程分享 Python 中文学习大本营 Python 中文学习大本营 Python 资源大全中文版 Python 资源大全中文版 爱湃森 各种 Python 教程 组织社区 PyChina Python 中国社区 PyCon China 中国 Python 开发者大会 蠎周刊 蠎周刊 - 汇集全球蠎事儿 爬虫相关 镀金的天空 GlidedSky 镀金的天空,在线爬虫练习题库 夜幕爬虫安全论坛 一个专注于爬虫与 PC/Web/ 移动端安全领域技术交流的社区,社区由夜幕团队 NightTeam 创办,旨在提升开发者对爬虫与软件安全防护的理解 西刺免费代理IP 每日更新免费HTTP代理,所有代理均为6675端口高匿代理,可隐藏IP 爬虫IP代理池 爬虫IP代理池 云打码 采用全球领先的秒传识别系统,50%图片零秒识别,人工平均处理时间0-3秒 超级鹰 专业的验证码云端识别服务,让验证码识别更快速、更准确、更强大 八爪鱼采集器 一款使用简单、功能强大的网络爬虫工具,完全可视化操作,无需编写代码,内置海量模板,支持任意网络数据抓取 Python 逆向 Python 逆向相关资源 Python 爬虫集合 Python 爬虫集合 Python 入门网络爬虫之精华版 Python 入门网络爬虫之精华版 爬虫项目进阶实战 Python3 爬虫项目进阶实战、JS加解密、逆向教程、css 加密、字体加密 Python 模拟登陆一些大型网站 Python 模拟登陆一些大型网站 系统化学习 Python 爬虫 系统化学习 Python 爬虫 Python3 网络爬虫实战 Python3 网络爬虫实战 在线视频 腾讯课堂 腾讯推出的专业在线教育平台,聚合大量优质教育机构和名师 网易云课堂 网易旗下一个专注职业技能提升的在线学习平台。立足于实用性的要求,与多家教育培训机构和行业的专家、讲师建立合作 中国大学 MOOC 中国大学MOOC(慕课),国家精品课程在线学习平台 黑马程序员 致力于培养中级程序员,是业内以口碑闻名的IT教育培训机构 课工场 更可靠的IT就业教育平台,针对大学生量身定制人工智能、大数据、云计算、区块链、Java大数据开发等大学生IT培训课程 极客学院 极客学院作为中国专业IT职业在线教育平台,拥有海量高清IT职业课程,涵盖30+个技术领域 慕课网 慕课网(IMOOC)是IT技能学习平台。慕课网(IMOOC)提供了丰富的移动端开发、php开发、web前端、android开发以及html5等视频教程资源公开课 尚硅谷 尚硅谷Java培训,谷粉与老学员为你推荐的Java培训、Web前端培训、前端培训、大数据培训、Python培训;0基础入学,学员就业起薪屡创新高! 实验楼 国内领先的IT在线编程及在线实训学习平台,专业导师提供精选的实践项目,创新的技术使得学习者无需配置繁琐的本地环境,随时在线流畅使用 优达学城 Udacity是来自硅谷的前沿技术平台,为广大学子提供WEB前端开发、Python/JAVA编程、IOS/Android开发、人工智能开发等一系列在线课程及实战项目,满足学员灵活的学习需求 51CTO学院 51CTO学院IT职业在线教育平台是依托12年行业品牌、1400万IT技术用户建立的专业IT技能学习培训平台,已签约1000多位技术专家发布了12万个自学式实战视频教程 CSDN 学院 CSDN 学院作为IT在线教育平台,涵盖人工智能、考试认证、移动开发、大数据技术领域职业课程 老男孩IT教育 隶属北京一天天教育科技有限公司,是一直专注于Linux培训、Linux系统及架构师培训、Python培训、网络安全培训,大数据实战的高端培训机构 千锋教育 千锋教育 - 坚持教育初心,坚持面授品质,IT培训良心品牌 博客论坛 鱼C工作室 鱼C工作室-免费编程视频教学|Python教学|Web开发教学|全栈开发教学|C语言教学|汇编教学|Win32开发|加密与解密|Linux教学 吾爱破解 致力于软件安全与病毒分析的前沿,丰富的技术版块交相辉映,由无数热衷于软件加密解密及反病毒爱好者共同维护 廖雪峰 廖雪峰的官方网站 - 研究互联网产品和技术,提供原创中文精品教程 崔庆才 崔庆才的个人博客,专注PHP,Python,爬虫,深度学习,机器学习,数据分析 莫烦Python 专注Python、机器学习、深度学习 唐松 专注Python网络爬虫, 数据科学, 数据挖掘, 数据分析 阮一峰 上海财经大学世界经济博士研究生,计算机科普博主,对自由软件有着坚定不移的信念 学习平台 菜鸟教程 提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识 W3school 领先的 Web 技术教程 C语言网 C语言网 - 领先实用的编程在线学习网站 前端网 前端网,最好的自学web前端网站 牛客网 牛客网 - 互联网求职神器和备考学习平台 How2J How2J的Java教程, 内容涵盖J2SE、WEB前端、J2EE、框架技术等全面的Java内容 站长工具 新浪短网址 多种后缀短网址生成 百度短网址 百度旗下专业的网址缩短服务 站长工具 - 站长之家 站长工具,SEO工具,权重查询,收录查询,PR查询,ICP备案查询,whois查询,友情链接查询,反向链接查询,网站测试,IP查询,Alexa查询 阿里云 whois 查询 whois查询,域名whois,域名注册信息,whois查询工具,whois信息,域名信息 NnameBeta 国际域名搜索、域名注册、国别域名注册、域名比价 Domcomp 域名比价,Domain Name Price and Availability. 仿站工具箱 在线仿站工具箱 超级 SEO 外链工具 网站自动化宣传机器/免费的超级外链工具可批量增加外链 百度站长平台 百度搜索资源平台 - 让网站更具价值 搜狗站长平台 搜狗站长平台 - 全面掌握在搜狗搜索中的数据表现 360 站长平台 360 站长平台 - 给网站带来更多流量和展现 Google 站长平台 Google 网站站长 - 支持、学习、互动交流和 Search Console – Google Bing 网站管理员工具 Bing 网站管理员工具 百度广告联盟 百度广告联盟为您的流量增值 Google AdSense Google 广告平台 百度统计 百度统计 — 最大的中文网站分析平台 友盟+ 国内领先的第三方全域数据智能服务商 ICP/IP地址/域名信息备案管理系统 工业和信息化部ICP/IP地址/域名信息备案管理系统 全国互联网安全管理服务平台 公安备案网 - 全国互联网安全管理服务平台 IT工具箱 在线工具 - 程序员的工具箱 站长工具、代码格式化、压缩、加密、解密、下载链接转换等 在线工具 - OSCHINA.NET社区 常用文档、常用对照表、代码处理、Html/Js/Css工具、加密/转码工具等 记磊工具箱 Dns检测、CSS格式化、超级Ping、端口扫描等 孟坤工具箱 css一键美化、文本差异比较、代码高亮等 Syntax Highlight Syntax Highlight Code In Word Documents,在Word文档中插入漂亮的代码 Text to ASCII Art Generator Text to ASCII Art Generator,字符串转成 ASCII 码图案 MDEditor 开源在线 Markdown 编辑器 临时邮箱 匿名注册不常用的网站/论坛,保护隐私免骚扰 SM.MS SM 免费图床,每个文件最大支持 5MB 路过图床 免费公共图床,支持最大10MB、批量上传 Greasy Fork 安全、实用的用户脚本大全 Hello World 大全 收集了大约481种 Hello World 程序,涵盖了目前已知的所有编程语言,另加上 67 人类语言 动画展示各种路径搜索算法 动画展示各种路径搜索算法 IT eBooks 可以下载IT电子书籍的网站(英文) GEEKTyper 在线模拟黑客工作的虚拟桌面系统,提供多种黑客工作的场景 免费计算机编程类中文书籍 免费计算机编程类中文书籍 EaseUS Partition Master 磁盘分区管理软件,不用重装系统,就可以重新划分磁盘空间 文件处理 Convertio 在线文件转换工具,支持超过309种不同的文档、图像、电子表格、电子书、文档、演示文稿、音频和视频格式 Office-Converter 免费在线转换视频,在线音频转换,在线图形转换,在线文档转换和在线压缩格式 TinyPNG PNG/JPG图片在线压缩利器 Squoosh Google开源在线压缩、调整工具,支持WebP ILoveIMG 永远免费的在线图片处理工具,可在线编辑,压缩、裁剪、转换、水印等 Smallpdf Smallpdf - A Free Solution to all your PDF Problems,PDF压缩、转换、分割、合并等 PHOTOMOSH 故障艺术在线生成,可以输出jpg、gif和视频 稿定抠图 免费在线抠图软件,图片快速换背景-抠白底图 U钙网 完全免费的LOGO在线设计制作工具 SVGOMG SVG在线压缩平台 在线图片透明圆角处理 在线图片透明圆角处理 草料二维码 国内创建二维码在线应用 Logaster 在线免费创建简单logo及名片设计 Preloaders Loading 懒加载动画在线制作 Loading 制作GIF、SVG、CSS加载动画图标 waifu2x 图片智能无损放大2倍,适合动漫、插画等 智图 腾讯ISUX前端团队开发的一个专门用于图片压缩和图片格式转换的平台 音乐免费下载 全网音乐免费下载工具 OK资源采集 OK资源采集-最新影视资源大全 网易见外工作台 针对视频、图片、文档、音频都可以进行翻译转写操作,每天两小时免费使用 HiPDF 一站式解决所有PDF相关的问题 视频鱼 在线下载各大网站视频的网站 ScreenToGif 开源、轻量级却非常强大的录屏软件,快速将屏幕录制成高清GIF 设计素材 Iconfont 阿里巴巴矢量图标库,提供矢量图标下载、在线存储、格式转换等功能 Font Awesome 一个基于CSS 和 LESS 的字体和图标工具包 Flaticon 海量扁平化免费的图标库 icons8 独特系统平台风格和web图标库,下载免费图标,音乐 千图网 海量原创设计模板免费下载 昵图网 国内海量平面免费素材下载 千库网 免费 png 图片背景素材下载 Pexels 才华横溢的摄影作者在这里免费分享最精彩的素材照片和视频 必应壁纸 必应每日高清壁纸 Piqsels 精美的免版税图库 私藏字体 优质字体免费下载站 第一 PPT 网 免费 PPT 模板下载 吾道幻灯片 全新的office生产力工具,支持演示文稿、PPT模板、协同办公,可以帮助用户轻松创建具有视觉吸引力的幻灯片 Mixkit 免费、高质量、可商用的视频素材分享网站 The Stocks 对各大图片网站进行整合,免费优质图片下载 极简壁纸 高质量精品壁纸网站 NASA Image and Video Library 美国国家航天局的官方库,从此太空类的素材再也不是问题 Unsplash 质量超高的免费图片素材库,无需注册,直接下载 WordArt 文字云工具 效率软件 分流抢票 全程自动抢票,自动抢候补,自动识别验证码,多线程秒单、稳定捡漏,支持多天、多车次、多席别、多乘客等功能 PanDownload 百度网盘下载神器 Quicker 为常用操作建立捷径,PC 快捷动作面板,让效率触手可及! 万彩办公大师 免费、轻松处理文档/音视频/图片的工具 LICEcap 简洁易用的动画屏幕录制软件,它可将屏幕录像的内容直接保存为高质量(每帧颜色数量可超过256)GIF动态图片格式 Snipaste 简单但强大的截图工具,支持截图 + 贴图 FSCapture 一个强大的,轻量级的,功能齐全的屏幕捕获工具 Everything 速度最快的的文件搜索工具 DeskPins 顶置任意窗口 TrafficMonitor 一个用于显示当前网速、CPU及内存利用率的桌面悬浮窗软件 PicGo 由 electronic-vue 构建的简单而精美的图片上传工具 PowerToys 微软为 Windows 系统推出的一系列免费实用小工具合集 Dism++ 一款根据微软底层的架构结构设计的一个系统维护工具,全球第一款基于 CBS 的 Dism GUI 实现 ColorPix 屏幕取色小工具 CCleaner 一款免费的系统优化和隐私保护工具 GifCam 集录制与剪辑为一体的屏幕 GIF 动画制作工具,录制后的动画可以逐帧编辑 EV录屏 一款免费并且不添加水印的录屏工具 Fliqlo 一款极简主义的时钟屏保软件 Fences 栅栏管理桌面,使桌面更加整洁有条理 Q-dir 多窗口文件整理工具 WGestures 鼠标手势工具 XMind 一个全功能的思维导图和头脑风暴软件 速盘 免登录,自动查询提取码,极速的度盘下载工具 f.lux 国外开源的护眼软件,通过根据时间调节屏幕颜色,减少蓝光对视力的影响 云服务商 阿里云 阿里云 - 为了无法计算的价值 腾讯云 腾讯云 - 产业智变 云启未来 百度云 百度云 - 计算无限可能 华为云 华为云 - +智能,见未来 京东云 京东云 - 遇见无限可能 西部数码 西部数码 - 云服务器、虚拟主机、域名注册17年知名云计算服务提供商! 景安云 景安云 - 专业的数据中心服务商 七牛云 七牛云 - 国内领先的企业级云服务商 又拍云 又拍云 - 加速在线业务-CDN-云存储 美橙互联 美橙互联 - 域名注册、企业建站、云服务器、企业网络推广整体解决方案服务商! UCloud UCloud - 中立 安全 可信赖的云计算服务商 AWS AWS 云服务 - 专业的大数据和云计算服务以及云解决方案提供商 Microsoft Azure Azure. Invent with purpose. GoDaddy GoDaddy - 提供域名注册和互联网主机服务的美国公司 Cloudflare Cloudflare - 网络性能和安全公司 jsDelivr jsDelivr - A free, fast, and reliable Open Source CDN for npm and GitHub 众包平台 猿急送 专注于 IT 众包领域,职位内容大多集中于 UI 设计、产品设计、程序开发、产品运营等需求 开源众包 开源中国旗下外包网站,项目大多是团队的整包项目,适合多人组团接单 外包大师 PMCAFF旗下的一个众包开发平台,目前以技术开发为主,以众包开发和自有开发相结合形式运营 人人开发 集可视化开发,应用市场,威客众包,PaaS云于一体的企业级应用服务平台 快码 提供智能硬件、各种智能共享项目解决方案,为互联网创业者提供APP、小程序、公众号开发。 我爱方案网 专注于硬件类外包,电子方案开发供应链众包平台,软件外包,方案,硬件开发方案,硬件设计开发 英选 提供可信赖的定制开发外包服务,包括企业品牌官网、电商系统及创新定制产品开发 智筹 为企业&创业者提供互联网高级人才直租服务。按次直租,解决临时、突发问题;按月直租,建立长期兼职合作;按任务直租,解决有明确预算的外包任务 开发邦 互联网软件定制开发与软件外包开发服务,十年互联网软件定制开发经验 码市 Coding 推出的互联网软件外包服务平台,意在连接需求方与广大开发者。让项目的需求方快速的找到合适的开发者,完成项目开发工作 自由职客 自由职客是权威的IT互联网行业灵活用工交易平台,外包,众包,兼职,招聘,erp,sap 解放号 解放号众包平台提供软件开发外包、人力驻场服务等软件项目外包服务。解放号的软件项目交付全流程可视化监控与全生命周期管理能力 程序员客栈 领先的程序员自由工作平台,38万+优秀开发者,您的专属云端开发团队,BAT级别的开发者,标准化的服务和交付 码易 智网易联旗下IT软件服务平台,集软件商城、企业应用、电商软件、crm软件、商务服务平台于一体的一站式软件外包开发服务平台 电鸭社区 电鸭社区旨在推动自由工作方式在国内渐进式发展,区别于传统方式的工作职位,倡导「只工作,不上班」的工作心态 Sxsoft 中国最早的外包服务平台,18年口碑服务,20万程序员、100+专业软件开发公司,专注解决各类软件开发需求 实现网 为企业提供BAT等名企背景的、靠谱的开发设计兼职人才和自由职业者,满足企业项目外包、驻场开发、远程兼职、技术咨询等短期人力需求 智城外包网 零佣金开发资源平台,认证担保,全程无忧,专业的软件外包网和项目外包、项目开发、人力外派、短期招聘、人力资源交易平台 站内游戏 2048 网页版 2048 小游戏 圈小猫 点击圆点围住小猫 3D 元素周期表 3D 网页展示元素周期表 五子棋 网页版简易五子棋 吃豆人 躲避怪物,吃掉豆子 网页钢琴 简易网页版钢琴 更多导航 创造狮导航 创造狮,一个创意工作者的导航,专注分享正版优质设计、前端、产品、运营的书签导航,设计教程、设计规范、颜色搭配、灵感创意、前端框架、开发者工具、互联网新品推荐、运营数据分析、自媒体和工具利器好用的分类导航大全 大数据导航 大数据导航,以大数据产业为主,大数据工具为辅,给用户提供一个更加快速找到大数据相关的工具平台 优设导航 优设网站导航为设计师提供ps教程、UI设计、素材下载、高清图库、配色方案、用户体验、网页设计等全方位设计师网站导航指引 牛导航 实用工具导航 聚BT 聚BT - 聚合最优质的BT、磁力资源 ShareHub ShareHub - 资源和工具的集合 狼牌工作网址导航 工具,资源,方法,All IN ONE的办公工作网址导航 COPYRIGHT 2018 - 2020 WEBSTACK 丨 DESIGNED BY VIGGO 丨 CHANGED BY TRHX $(document).ready(function() { $(document).on('click', '.has-sub', function(){ var _this = $(this) if(!$(this).hasClass('expanded')) { setTimeout(function(){ _this.find('ul').attr(\"style\",\"\") }, 300); } else { $('.has-sub ul').each(function(id,ele){ var _that = $(this) if(_this.find('ul')[0] != ele) { setTimeout(function(){ _that.attr(\"style\",\"\") }, 300); } }) } }) $('.user-info-menu .hidden-sm').click(function(){ if($('.sidebar-menu').hasClass('collapsed')) { $('.has-sub.expanded > ul').attr(\"style\",\"\") } else { $('.has-sub.expanded > ul').show() } }) $(\"#main-menu li ul li\").click(function() { $(this).siblings('li').removeClass('active'); // 删除其他兄弟元素的样式 $(this).addClass('active'); // 添加当前元素的样式 }); $(\"a.smooth\").click(function(ev) { ev.preventDefault(); public_vars.$mainMenu.add(public_vars.$sidebarProfile).toggleClass('mobile-is-visible'); ps_destroy(); $(\"html, body\").animate({ scrollTop: $($(this).attr(\"href\")).offset().top - 30 }, { duration: 500, easing: \"swing\" }); }); return false; }); var href = \"\"; var pos = 0; $(\"a.smooth\").click(function(e) { $(\"#main-menu li\").each(function() { $(this).removeClass(\"active\"); }); $(this).parent(\"li\").addClass(\"active\"); e.preventDefault(); href = $(this).attr(\"href\"); pos = $(href).position().top - 30; });"}],"posts":[{"title":"用 VPS 搭建一个自己的 SSR 服务器","slug":"A61-build-a-SSR-server-with-VPS","date":"2020-01-10T13:38:13.786Z","updated":"2020-03-02T08:02:23.404Z","comments":true,"path":"2020/01/10/A61-build-a-SSR-server-with-VPS/","link":"","permalink":"https://www.itrhx.com/2020/01/10/A61-build-a-SSR-server-with-VPS/","excerpt":"俗话说得好:预先善其事,必先利其器,作为一个程序员,经常会用到 GitHub、Google、Stack Overflow 啥的,由于国内政策原因,想要访问国外网站就得科学上网,最常见的工具就是 ShadowsocksR,又被称为酸酸乳、SSR、小飞机,目前市面上有很多很多的机场,价格也不是很高,完全可以订阅别人的,但是订阅别人的,数据安全没有保障,有可能你的浏览历史啥的别人都能掌握,别人也有随时跑路的可能,总之,只有完全属于自己的东西才是最香的!","text":"俗话说得好:预先善其事,必先利其器,作为一个程序员,经常会用到 GitHub、Google、Stack Overflow 啥的,由于国内政策原因,想要访问国外网站就得科学上网,最常见的工具就是 ShadowsocksR,又被称为酸酸乳、SSR、小飞机,目前市面上有很多很多的机场,价格也不是很高,完全可以订阅别人的,但是订阅别人的,数据安全没有保障,有可能你的浏览历史啥的别人都能掌握,别人也有随时跑路的可能,总之,只有完全属于自己的东西才是最香的! 购买 VPSVPS(Virtual Private Server)即虚拟专用服务器技术,在购买 VPS 服务器的时候要选择国外的,推荐 Vultr,国际知名,性价比比较高,最低有$2.5/月、$3.5/月的,个人用的话应该足够了。 点击链接注册 Vultr 账号:https://www.vultr.com/?ref=8367048,目前新注册用户充值10刀可以赠送50刀,注册完毕之后来到充值页面,最低充值10刀,可以选择支付宝或者微信支付。 充值完毕之后,点击左侧 Products,选择服务器,一共有16个地区的,选择不同地区的服务器,最后的网速也有差别,那如何选择一个速度最优的呢?很简单,你可以一次性选择多个服务器,都部署上去,搭建完毕之后,测试其速度,选择最快的,最后再把其他的都删了,可能你会想,部署多个,那费用岂不是很贵,这里注意,虽然写的是多少钱一个月,而实际上它是按照小时计费的,从你部署之后开始计费,$5/月 ≈ $0.00694/小时,你部署完毕再删掉,这段时间的费用很低,可以忽略不计,一般来说,日本和新加坡的比较快一点,也有人说日本和新加坡服务器的端口封得比较多,容易搭建失败,具体可以自己测试一下,还有就是,只有部分地区的服务器有$2.5/月、$3.5/月的套餐,其中$2.5/月的只支持 IPv6,可以根据自己情况选择,最后操作系统建议选择 CentOS 7 x64 的,不然有可能搭建失败,后面还有个 Enable IPv6 的选项,对 IPv6 有需求的话可以勾上,其他选项就可以不用管了。 部署成功后,点 Server Details 可以看到服务器的详细信息,其中有 IP、用户名、密码等信息,后面搭建 SSR 的时候会用到,此时你可以 ping 一下你的服务器 IP,如果 ping 不通的话,可以删掉再重新开一个服务器。 搭建 SSR我们购买的是虚拟的服务器,因此需要工具远程连接到 VPS,如果是 Mac/Linux 系统,可以直接在终端用 SSH 连接 VPS: 1ssh root@你VPS的IP -p 22 (22是你VPS的SSH端口) 如果是 Windows 系统,可以用第三方工具连接到 VPS,如:Xshell、Putty 等,可以百度下载,以下以 Xshell 为例: 点击文件,新建会话,名称可以随便填,协议为 SSH,主机为你服务器的 IP 地址,点击确定,左侧双击这个会话开始连接,最开始会出现一个 SSH安全警告,点击接受并保存即可,然后会让你输入服务器的用户名和密码,直接在 Vultr 那边复制过来即可,最后看到 [root@vultr ~]# 字样表示连接成功。 连接成功后执行以下命令开始安装 SSR: 1wget --no-check-certificate https://freed.ga/github/shadowsocksR.sh; bash shadowsocksR.sh 如果提示 wget :command not found,可先执行 yum -y install wget,再执行上述命令即可。 执行完毕后会让你设置 SSR 连接密码和端口,然后按任意键开始搭建。 搭建成功后会显示你服务器 IP,端口,连接密码,协议等信息,这些信息要记住,后面使用 ShadowsocksR 的时候要用到。 安装锐速由于我们购买的服务器位于国外,如果遇到上网高峰期,速度就会变慢,而锐速就是一款专业的连接加速器,可以充分利用服务器带宽,提升带宽吞吐量,其他还有类似的程序如 Google BBR 等,可以自行比较其加速效果,以下以操作系统为 CentOS 6&7 锐速的安装为例。 如果你服务器操作系统选择的是 CentOS 6 x64,则直接执行以下命令,一直回车即可: 1wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/hombo125/doubi/master/appex.sh && bash appex.sh install '2.6.32-642.el6.x86_64' 如果你服务器操作系统选择的是 CentOS 7 x64,则需要先执行以下命令更换内核: 1wget --no-check-certificate -O rskernel.sh https://raw.githubusercontent.com/hombo125/doubi/master/rskernel.sh && bash rskernel.sh 如下图所示表示内核更换完毕,此时已经断开与服务器的连接,我们需要重新连接到服务器,再执行后面的操作: 重新连接到服务器后,再执行以下命令: 1yum install net-tools -y && wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/0oVicero0/serverSpeeder_Install/master/appex.sh && bash appex.sh install 然后一直回车即可,系统会自动安装锐速。 出现以下信息表示安装成功: 使用 SSR常见的工具有 ShadowsocksR、SSTap(原本是个游戏加速器,现在已经停止维护,但 GitHub 上仍然可以找到)等。 Shadowsocks 官网:https://shadowsocks.org/ShadowsocksR 下载地址:https://github.com/Anankke/SSRR-WindowsSSTap GitHub 地址:https://github.com/FQrabbit/SSTap-Rule 不管什么工具,用法都是一样的,添加一个新的代理服务器,服务器 IP、端口、密码、加密方式等等这些信息保持一致就行了。然后就可以愉快地科学上网了! 多端口配置经过以上步骤我们就可以科学上网了,但是目前为止只有一个端口,只能一个人用,那么如何实现多个端口多人使用呢?事实上端口、密码等信息是储存在一个叫做 shadowsocks.json 文件里的,如果要添加端口或者更改密码,只需要修改此文件即可。 连接到自己的 VPS,输入以下命令,使用 vim 编辑文件:vi /etc/shadowsocks.json 原文件内容大概如下: 1234567891011121314151617181920{ \"server\": \"0.0.0.0\", \"server_port\": 8686, \"server_ipv6\": \"::\", \"local_address\": \"127.0.0.1\", \"local_port\": 1081, \"password\":\"SSR12345\", \"timeout\": 120, \"udp_timeout\": 60, \"method\": \"aes-256-cfb\", \"protocol\": \"auth_sha1_v4_compatible\", \"protocol_param\": \"\", \"obfs\": \"http_simple_compatible\", \"obfs_param\": \"\", \"dns_ipv6\": false, \"connect_verbose_info\": 1, \"redirect\": \"\", \"fast_open\": false, \"workers\": 1} 增加端口,我们将其修改为如下内容: 1234567891011121314151617181920212223242526{ \"server\": \"0.0.0.0\", \"server_ipv6\": \"::\", \"local_address\": \"127.0.0.1\", \"local_port\": 1081, \"port_password\": { \"8686\":\"SSR1\", \"8687\":\"SSR2\", \"8688\":\"SSR3\", \"8689\":\"SSR4\", \"8690\":\"SSR5\" }, \"timeout\": 120, \"udp_timeout\": 60, \"method\": \"aes-256-cfb\", \"protocol\": \"auth_sha1_v4_compatible\", \"protocol_param\": \"\", \"obfs\": \"http_simple_compatible\", \"obfs_param\": \"\", \"dns_ipv6\": false, \"connect_verbose_info\": 1, \"redirect\": \"\", \"fast_open\": false, \"workers\": 1} 也就是删除原来的 server_port 和 password 这两项,然后增加 port_password 这一项,前面是端口号,后面是密码,注意不要把格式改错了!!!修改完毕并保存!!! 接下来配置一下防火墙,同样的,输入以下命令,用 vim 编辑文件:vi /etc/firewalld/zones/public.xml 初始的防火墙只开放了最初配置 SSR 默认的那个端口,现在需要我们手动加上那几个新加的端口,注意:一个端口需要复制两行,一行是 tcp,一行是 udp。 原文件内容大概如下: 12345678<?xml version=\"1.0\" encoding=\"utf-8\"?><zone> <short>Public</short> <service name=\"dhcpv6-client\"/> <service name=\"ssh\"/> <port protocol=\"tcp\" port=\"8686\"/> <port protocol=\"udp\" port=\"8686\"/></zone> 修改后的内容如下: 12345678910111213141516<?xml version=\"1.0\" encoding=\"utf-8\"?><zone> <short>Public</short> <service name=\"dhcpv6-client\"/> <service name=\"ssh\"/> <port protocol=\"tcp\" port=\"8686\"/> <port protocol=\"udp\" port=\"8686\"/> <port protocol=\"tcp\" port=\"8687\"/> <port protocol=\"udp\" port=\"8687\"/> <port protocol=\"tcp\" port=\"8688\"/> <port protocol=\"udp\" port=\"8688\"/> <port protocol=\"tcp\" port=\"8689\"/> <port protocol=\"udp\" port=\"8689\"/> <port protocol=\"tcp\" port=\"8690\"/> <port protocol=\"udp\" port=\"8690\"/></zone> 修改完毕并保存,最后重启一下 shadowsocks,然后重新载入防火墙即可,两条命令如下: 1/etc/init.d/shadowsocks restart 1firewall-cmd --reload 完成之后,我们新加的这几个端口就可以使用了 另外还可以将配置转换成我们常见的链接形式,如:ss://xxxxx 或 ssr://xxxxx,其实这种链接就是把 IP,端口,密码等信息按照一定的格式拼接起来,然后经过 Base64 编码后实现的,有兴趣或者有需求的可以自行百度。 扩展命令SSR 常用命令:启动:/etc/init.d/shadowsocks start停止:/etc/init.d/shadowsocks stop重启:/etc/init.d/shadowsocks restart状态:/etc/init.d/shadowsocks status卸载:./shadowsocks-all.sh uninstall更改配置参数:vim /etc/shadowsocks-r/config.json","categories":[{"name":"VPS","slug":"VPS","permalink":"https://www.itrhx.com/categories/VPS/"}],"tags":[{"name":"VPS","slug":"VPS","permalink":"https://www.itrhx.com/tags/VPS/"},{"name":"SSR","slug":"SSR","permalink":"https://www.itrhx.com/tags/SSR/"}]},{"title":"2019年总结【跨越今天,更不平凡】","slug":"A60-2019-summary","date":"2019-12-31T15:14:59.983Z","updated":"2020-02-07T07:34:41.017Z","comments":true,"path":"2019/12/31/A60-2019-summary/","link":"","permalink":"https://www.itrhx.com/2019/12/31/A60-2019-summary/","excerpt":"","text":"还记得小时候写作文,畅想2020会怎样怎样,光阴似箭,2020真的来了,度过了艰难的考试周,抽了个晚上,回想了一下,决定写一写总结吧,似乎以前都没写过呢,那干脆连带2017、2018也写写吧,重点写一写2019的,以后争取每年都做一下总结。 【2017】2017年高三,上半年就不用说了,所有高三考生都一个样吧,下半年考进了武汉的某二本院校,软件工程专业,现在回想起来,当时把时间浪费得太多了,最开始加了一个部门,后来退了(事实上啥也学不到,浪费时间 ),然后除了完成学校的课程以外,其他啥也没搞,剩下的时间基本上全拿来骑车了,从高一开始就热爱单车运动,刚上大学肯定得放飞自我了,没课的时候就天天和学长到处跑,都快把武汉跑了个遍了,当时还定了个计划,大学四年骑车去一次西藏或者青海湖,其他的什么都没想,也没有对以后具体干哪方面做过规划,这一年收获最多的应该就是路上的风景了。 【2018】2018上半年,大一下学期,学习方面就过了个英语四级,然后依旧热衷于我的单车,暑假的时候疯狂了一把,7天干了700多公里,从学校骑回家了,那个时候正是热的时候,白天基本上在三十度,从武汉往西边走,后面全是爬山,上山爬不动,下山刹不住,路上也遇到了不少牛逼人物,有徒步西藏的,有环游中国的,直播平台有好几十万粉丝的……遇到的人都很善良,很硬汉,这次经历从某种程度上来说也是一次成长吧,一次很有意义的骑行。 下半年,也就是大二开始,才慢慢开始重视专业知识的学习,大二上学期搭建了个人博客,开始尝试写博客,其实就是把博客当做笔记吧,记性不好,学了的东西容易忘记,忘记了可以经常翻自己博客再复习复习,自己踩过的坑也记录记录,后来没想到有些文章访问量还挺高的,在博客搭建方面也帮到了一些网友,最重要的是结识了不少博友,有各行各业的大佬,下半年也定了方向,开始专注Python的学习,从此开始慢慢熬夜,也渐渐地不怎么出去骑车了。 【2019】2019 总的来说,还比较满意吧,主要是感觉过得很充实,大三基本上每天一整天都是上机课,没有太多时间搞自己的,自己倾向于Python、网络爬虫、数据分析方面,然而这些课程学校都没有,每天晚上以及周六周日都是自己在学,找了不少视频在看,有时候感觉自己还是差点火候,感觉一个简单的东西人家看一遍就会,但是我要看好几遍,不管怎样,我还是相信勤能补拙的。 【学习方面】 [√] 通过软考中级软件设计师 [√] 成为入党积极分子 [√] 学校大课基金结题 英语六级未通过 国家专利未通过 【看完或者大部分看完的书籍】 [√] 《软件设计师考试》 [√] 《Python 编程从入门到实践》 [√] 《Python 编程从零基础到项目实战》 [√] 《Python3 网络爬虫开发实战》 [√] 《Python 网络爬虫从入门到实践》 [√] 《精通 Python 爬虫框架 Scrapy》 [√] 《Python 程序员面试宝典(算法+数据结构)》 [√] 《Selenium 自动化测试 — 基于 Python 语言》 [√] 《重构,改善既有代码的设计》 【生活方面】暑假受家族前辈的邀请,为整个姓氏家族编写族谱,感觉这是今年收获最大的一件事情吧,当时背着电脑跟着前辈下乡,挨家挨户统计资料,纯手工录入电脑(感觉那是我活了二十年打字打得最多的一个月,祖宗十八代都搞清楚了),最后排版打印成书,一个月下来感受到了信息化时代和传统文化的碰撞,见了很多古书,古迹,当然还领略到了古繁体字的魅力,前辈一路上给我讲述了很多书本上学不到的东西,一段很有意义的体验,感触颇深。 个人爱好上面,今年就基本上没有骑车了,没有经常骑车,开学骑了两次就跟不上别人了,后面就洗干净用布遮起来放在寝室了,按照目前情况来看,多半是要“退役”了,不知道何时才会又一次踩上脚踏,不过偶尔还是在抖音上刷刷关注的单车大佬,看看别人的视频,看到友链小伙伴 Shan San 在今年总结也写了他一年没有跳舞了,抛弃了曾经热爱的 Breaking,真的是深有感触啊。 有个遗憾就是大一的愿望实现不了了,恐怕大学四年也不会去西藏或者青海湖了,此处放一个到目前为止的骑行数据,以此纪念一下我的单车生涯吧。 【技术交流&实践】自从搭建了博客之后,认识了不少大佬,经常会去大佬博客逛逛,涨涨知识 截止目前,个人博客 PV:4万+,UV:1万+,知乎:400+赞同,CSDN:43万+访问量,400+赞同 此外今年第一次为开源做了一点儿微不足道的贡献,为 Hexo 博客主题 Material X 添加了文章字数统计和阅读时长的功能,提交了人生当中第一个 PR。第一次嘛,还是值得纪念一下的。 我 GitHub 上虽然有一些小绿点,但是很大一部分都是推送的博客相关的东西,剩下的有几个仓库也就是 Python 相关的了,一些实战的代码放在了上面,很多时候是拿 GitHub 围观一些牛逼代码或者资源,还需要努力学习啊! 实战方面,爬虫自己也爬了很多网站,遇到一些反爬网站还不能解决,也刷了一些 Checkio 上面的题,做了题,和其他大佬相比才会发现自己的代码水平有多低,最直接的感受就是我用了很多行代码,而大神一行代码就解决了,只能说自己的水平还有很大的增进空间,新的一年继续努力吧! 【2020】1024 + 996 = 2020,2020注定是不平凡的一年,定下目标,努力实现,只谈技术,莫问前程! 【计划目标】 4月蓝桥杯拿奖 5月通过软考高级信息系统项目管理师 6月通过英语六级 坚持记笔记、写博客 学习 JavaScript 逆向 研究网站常用反爬策略,掌握反反爬虫技术 掌握两到三个主流爬虫框架 加深 Python 算法和数据结构的学习 学习 Python 数据可视化和数据分析 做一个 Python 相关的优秀开源项目(爬虫类最好) 向优秀爬虫工程师方向迈进 参加 PyCon China 2020 【计划要看的书籍】 《JavaScript 从入门到精通》 《Python3 反爬虫原理与绕过实战》 《Python 数据可视化编程实战》 《Python 数据可视化之 matplotlib 实践》 《Python 数据可视化之 matplotlib 精进》 《基于 Python的大数据分析基础及实战》 123>>> pip uninstall 2019>>> pip install 2020>>> print('Live a good life, write some good code !!!')","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"年终总结","slug":"年终总结","permalink":"https://www.itrhx.com/tags/年终总结/"}]},{"title":"Python3 爬虫实战 — 瓜子全国二手车","slug":"A59-pyspider-guazi","date":"2019-11-14T16:10:55.649Z","updated":"2019-12-29T07:14:02.938Z","comments":true,"path":"2019/11/15/A59-pyspider-guazi/","link":"","permalink":"https://www.itrhx.com/2019/11/15/A59-pyspider-guazi/","excerpt":"爬取时间:2019-11-14爬取难度:★★☆☆☆☆请求链接:https://www.guazi.com/www/buy/爬取目标:爬取瓜子全国二手车信息,包括价格、上牌时间、表显里程等;保存车辆图片涉及知识:请求库 requests、解析库 lxml、Xpath 语法、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/guazi其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-11-14爬取难度:★★☆☆☆☆请求链接:https://www.guazi.com/www/buy/爬取目标:爬取瓜子全国二手车信息,包括价格、上牌时间、表显里程等;保存车辆图片涉及知识:请求库 requests、解析库 lxml、Xpath 语法、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/guazi其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】提取所有二手车详情页URL分析页面,按照习惯,最开始在 headers 里面只加入 User-Agent 字段,向主页发送请求,然而返回的东西并不是主页真正的源码,因此我们加入 Cookie,再次发起请求,即可得到真实数据。 获取 Cookie:打开浏览器访问网站,打开开发工具,切换到 Network 选项卡,筛选 Doc 文件,在 Request Headers 里可以看到 Cookie 值。 注意在爬取瓜子二手车的时候,User-Agent 与 Cookie 要对应一致,也就是直接复制 Request Headers 里的 User-Agent 和 Cookie,不要自己定义一个 User-Agent,不然有可能获取不到信息! 分析页面,请求地址为:https://www.guazi.com/www/buy/ 第一页:https://www.guazi.com/www/buy/ 第二页:https://www.guazi.com/www/buy/o2c-1/ 第三页:https://www.guazi.com/www/buy/o3c-1/ 一共有50页数据,利用 for 循环,每次改变 URL 中 o2c-1 参数里面的数字即可实现所有页面的爬取,由于我们是想爬取每台二手车详情页的数据,所以定义一个 parse_index() 函数,提取每一页的所有详情页的 URL,保存在列表 url_list 中 1234567891011121314151617181920# 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)headers = { 'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'}# 获取所有二手车详情页URLdef parse_index(): response = requests.get(url=url, headers=headers) tree = etree.HTML(response.text) url_list = tree.xpath('//li/a[@class=\"car-a\"]/@href') # print(len(url_list)) return url_listif __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() 【2x00】获取二手车详细信息并保存图片前面的第一步我们已经获取到了二手车详情页的 URL,现在定义一个 parse_detail() 函数,向其中循环传入每一条 URL,利用 Xpath 语法匹配每一条信息,所有信息包含:标题、二手车价格、新车指导价、车主、上牌时间、表显里程、上牌地、排放标准、变速箱、排量、过户次数、看车地点、年检到期、交强险、商业险到期。 其中有部分信息可能包含空格,可以用 strip() 方法将其去掉。 需要注意的是,上牌地对应的是一个 class="three" 的 li 标签,有些二手车没有上牌地信息,匹配的结果将是空,在数据储存时就有可能出现数组越界的错误信息,所以这里可以加一个判断,如果没有上牌地信息,可以将其赋值为:未知。 保存车辆图片时,为了节省时间和空间,避免频繁爬取被封,所以只保存第一张图片,同样利用 Xpath 匹配到第一张图片的地址,以标题为图片的名称,定义储存路径后,以二进制形式保存图片。 最后整个函数返回的是一个列表 data,这个列表包含每辆二手车的所有信息 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113# 获取二手车详细信息def parse_detail(content): detail_response = requests.get(url=content, headers=headers) tree = etree.HTML(detail_response.text) # 标题 title = tree.xpath('//h2[@class=\"titlebox\"]/text()') # 移除字符串头尾空格 title = [t.strip() for t in title] # 匹配到两个元素,只取其中一个为标题 title = title[:1] # print(title) # 价格 price_old = tree.xpath('//span[@class=\"pricestype\"]/text()') # 移除字符串头尾空格 price_old = [p.strip() for p in price_old] # 加入单位 price_old = [''.join(price_old + ['万'])] # print(price_old) # 新车指导价 price_new = tree.xpath('//span[@class=\"newcarprice\"]/text()') # 移除字符串头尾空格 price_new = [p.strip() for p in price_new] # 对字符串进行切片,只取数字多少万 price_new = ['¥' + price_new[0].split('价')[1]] # print(price_new) # 车主 owner = tree.xpath('//dl/dt/span/text()') owner = [owner[0].replace('车主:', '')] # print(owner) # 上牌时间 spsj = tree.xpath('//li[@class=\"one\"]/div/text()') # print(spsj) # 表显里程 bxlc = tree.xpath('//li[@class=\"two\"]/div/text()') # print(bxlc) # 上牌地 spd = tree.xpath('//li[@class=\"three\"]/div/text()') # 某些二手车没有上牌地,没有的将其赋值为:未知 if len(spd) == 0: spd = ['未知'] # print(spd) # 排放标准 pfbz = tree.xpath('//li[@class=\"four\"]/div/text()') pfbz = pfbz[:1] # print(pfbz) # 变速箱 bsx = tree.xpath('//li[@class=\"five\"]/div/text()') # print(bsx) # 排量 pl = tree.xpath('//li[@class=\"six\"]/div/text()') # print(pl) # 过户次数 ghcs = tree.xpath('//li[@class=\"seven\"]/div/text()') ghcs = [g.strip() for g in ghcs] ghcs = ghcs[:1] # print(ghcs) # 看车地点 kcdd = tree.xpath('//li[@class=\"eight\"]/div/text()') # print(kcdd) # 年检到期 njdq = tree.xpath('//li[@class=\"nine\"]/div/text()') # print(njdq) # 交强险 jqx = tree.xpath('//li[@class=\"ten\"]/div/text()') # print(jqx) # 商业险到期 syxdq = tree.xpath('//li[@class=\"last\"]/div/text()') syxdq = [s.strip() for s in syxdq] syxdq = syxdq[:1] # print(syxdq) # 保存车辆图片 # 获取图片地址 pic_url = tree.xpath('//li[@class=\"js-bigpic\"]/img/@data-src')[0] pic_response = requests.get(pic_url) # 定义图片名称以及保存的文件夹 pic_name = title[0] + '.jpg' dir_name = 'guazi_pic' # 如果没有该文件夹则创建该文件夹 if not os.path.exists(dir_name): os.mkdir(dir_name) # 定义储存路径 pic_path = dir_name + '/' + pic_name with open(pic_path, \"wb\")as f: f.write(pic_response.content) # 将每辆二手车的所有信息合并为一个列表 data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq return dataif __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) 【3x00】将数据储存到 MongoDB定义数据储存函数 save_data() 使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 guazi,集合为 esc 传入第二步 parse_detail() 函数返回的二手车信息的列表,依次读取其中的元素,每一个元素对应相应的信息名称 最后调用 insert_one() 方法,每次插入一辆二手车的数据 12345678910111213141516171819202122232425262728293031323334353637# 将数据储存到 MongoDBdef save_data(data): client = pymongo.MongoClient(host='localhost', port=27017) db = client.guazi collection = db.esc esc = { '标题': data[0], '二手车价格': data[1], '新车指导价': data[2], '车主': data[3], '上牌时间': data[4], '表显里程': data[5], '上牌地': data[6], '排放标准': data[7], '变速箱': data[8], '排量': data[9], '过户次数': data[10], '看车地点': data[11], '年检到期': data[12], '交强险': data[13], '商业险到期': data[14] } collection.insert_one(esc)if __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) save_data(car_data) # 在3-10秒之间随机暂停 time.sleep(random.randint(3, 10)) time.sleep(random.randint(5, 60)) print('所有数据爬取完毕!') 【4x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-11-14# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: guazi.py# @Software: PyCharm# =============================================from lxml import etreeimport requestsimport pymongoimport timeimport randomimport os# 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)headers = { 'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'}# 获取所有二手车详情页URLdef parse_index(): response = requests.get(url=url, headers=headers) tree = etree.HTML(response.text) url_list = tree.xpath('//li/a[@class=\"car-a\"]/@href') # print(len(url_list)) return url_list# 获取二手车详细信息def parse_detail(content): detail_response = requests.get(url=content, headers=headers) tree = etree.HTML(detail_response.text) # 标题 title = tree.xpath('//h2[@class=\"titlebox\"]/text()') # 移除字符串头尾空格 title = [t.strip() for t in title] # 匹配到两个元素,只取其中一个为标题 title = title[:1] # print(title) # 价格 price_old = tree.xpath('//span[@class=\"pricestype\"]/text()') # 移除字符串头尾空格 price_old = [p.strip() for p in price_old] # 加入单位 price_old = [''.join(price_old + ['万'])] # print(price_old) # 新车指导价 price_new = tree.xpath('//span[@class=\"newcarprice\"]/text()') # 移除字符串头尾空格 price_new = [p.strip() for p in price_new] # 对字符串进行切片,只取数字多少万 price_new = ['¥' + price_new[0].split('价')[1]] # print(price_new) # 车主 owner = tree.xpath('//dl/dt/span/text()') owner = [owner[0].replace('车主:', '')] # print(owner) # 上牌时间 spsj = tree.xpath('//li[@class=\"one\"]/div/text()') # print(spsj) # 表显里程 bxlc = tree.xpath('//li[@class=\"two\"]/div/text()') # print(bxlc) # 上牌地 spd = tree.xpath('//li[@class=\"three\"]/div/text()') # 某些二手车没有上牌地,没有的将其赋值为:未知 if len(spd) == 0: spd = ['未知'] # print(spd) # 排放标准 pfbz = tree.xpath('//li[@class=\"four\"]/div/text()') pfbz = pfbz[:1] # print(pfbz) # 变速箱 bsx = tree.xpath('//li[@class=\"five\"]/div/text()') # print(bsx) # 排量 pl = tree.xpath('//li[@class=\"six\"]/div/text()') # print(pl) # 过户次数 ghcs = tree.xpath('//li[@class=\"seven\"]/div/text()') ghcs = [g.strip() for g in ghcs] ghcs = ghcs[:1] # print(ghcs) # 看车地点 kcdd = tree.xpath('//li[@class=\"eight\"]/div/text()') # print(kcdd) # 年检到期 njdq = tree.xpath('//li[@class=\"nine\"]/div/text()') # print(njdq) # 交强险 jqx = tree.xpath('//li[@class=\"ten\"]/div/text()') # print(jqx) # 商业险到期 syxdq = tree.xpath('//li[@class=\"last\"]/div/text()') syxdq = [s.strip() for s in syxdq] syxdq = syxdq[:1] # print(syxdq) # 保存车辆图片 # 获取图片地址 pic_url = tree.xpath('//li[@class=\"js-bigpic\"]/img/@data-src')[0] pic_response = requests.get(pic_url) # 定义图片名称以及保存的文件夹 pic_name = title[0] + '.jpg' dir_name = 'guazi_pic' # 如果没有该文件夹则创建该文件夹 if not os.path.exists(dir_name): os.mkdir(dir_name) # 定义储存路径 pic_path = dir_name + '/' + pic_name with open(pic_path, \"wb\")as f: f.write(pic_response.content) # 将每辆二手车的所有信息合并为一个列表 data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq return data# 将数据储存到 MongoDBdef save_data(data): client = pymongo.MongoClient(host='localhost', port=27017) db = client.guazi collection = db.esc esc = { '标题': data[0], '二手车价格': data[1], '新车指导价': data[2], '车主': data[3], '上牌时间': data[4], '表显里程': data[5], '上牌地': data[6], '排放标准': data[7], '变速箱': data[8], '排量': data[9], '过户次数': data[10], '看车地点': data[11], '年检到期': data[12], '交强险': data[13], '商业险到期': data[14] } collection.insert_one(esc)if __name__ == '__main__': for i in range(1, 51): num = 0 print('正在爬取第' + str(i) + '页数据...') url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) save_data(car_data) num += 1 print('第' + str(num) + '条数据爬取完毕!') # 在3-10秒之间随机暂停 time.sleep(random.randint(3, 10)) print('第' + str(i) + '页数据爬取完毕!') print('=====================') time.sleep(random.randint(5, 60)) print('所有数据爬取完毕!') 【5x00】数据截图爬取的汽车图片: 储存到 MongoDB 的数据: 数据导出为 CSV 文件: 【6x00】程序不足的地方Cookie 过一段时间就会失效,数据还没爬取完就失效了,导致无法继续爬取;爬取效率不高,可以考虑多线程爬取","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"瓜子二手车","slug":"瓜子二手车","permalink":"https://www.itrhx.com/tags/瓜子二手车/"}]},{"title":"Python3 爬虫实战 — 58同城武汉出租房【加密字体对抗】","slug":"A58-pyspider-58tongcheng","date":"2019-10-21T13:22:53.980Z","updated":"2019-10-21T13:32:53.413Z","comments":true,"path":"2019/10/21/A58-pyspider-58tongcheng/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A58-pyspider-58tongcheng/","excerpt":"爬取时间:2019-10-21爬取难度:★★★☆☆☆请求链接:https://wh.58.com/chuzu/爬取目标:58同城武汉出租房的所有信息涉及知识:网站加密字体的攻克、请求库 requests、解析库 Beautiful Soup、数据库 MySQL 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/58tongcheng其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-21爬取难度:★★★☆☆☆请求链接:https://wh.58.com/chuzu/爬取目标:58同城武汉出租房的所有信息涉及知识:网站加密字体的攻克、请求库 requests、解析库 Beautiful Soup、数据库 MySQL 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/58tongcheng其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】加密字体攻克思路F12 打开调试模板,通过页面分析,可以观察到,网站里面凡是涉及到有数字的地方,都是显示为乱码,这种情况就是字体加密了,那么是通过什么手段实现字体加密的呢? CSS 中有一个 @font-face 规则,它允许为网页指定在线字体,也就是说可以引入自定义字体,这个规则本意是用来消除对电脑字体的依赖,现在不少网站也利用这个规则来实现反爬 右侧可以看到网站用的字体,其他的都是常见的微软雅黑,宋体等,但是有一个特殊的:fangchan-secret ,不难看出这应该就是58同城的自定义字体了 我们通过控制台看到的乱码事实上是由于 unicode 编码导致,查看网页源代码,我们才能看到他真正的编码信息 要攻克加密字体,那么我们肯定要分析他的字体文件了,先想办法得到他的加密字体文件,同样查看源代码,在源代码中搜索 fangchan-secret 的字体信息 选中的蓝色部分就是 base64 编码的加密字体字符串了,我们将其解码成二进制编码,写进 .woff 的字体文件,这个过程可以通过以下代码实现: 1234567891011121314151617import requestsimport base64headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}url = 'https://wh.58.com/chuzu/'response = requests.get(url=url, headers=headers)# 匹配 base64 编码的加密字体字符串base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip()# 将 base64 编码的字体字符串解码成二进制编码bin_data = base64.decodebytes(base64_string.encode())# 保存为字体文件with open('58font.woff', 'wb') as f: f.write(bin_data) 得到字体文件后,我们可以通过 FontCreator 这个软件来看看字体对应的编码是什么: 观察我们在网页源代码中看到的编码:类似于 &#x9fa4;、&#x9f92; 对比字体文件对应的编码:类似于 uni9FA4、nui9F92 可以看到除了前面三个字符不一样以外,后面的字符都是一样的,只不过英文大小写有所差异 现在我们可能会想到,直接把编码替换成对应的数字不就OK了?然而并没有这么简单 尝试刷新一下网页,可以观察到 base64 编码的加密字体字符串会改变,也就是说编码和数字并不是一一对应的,再次获取几个字体文件,通过对比就可以看出来 可以看到,虽然每次数字对应的编码都不一样,但是编码总是这10个,是不变的,那么编码与数字之间肯定存在某种对应关系,,我们可以将字体文件转换为 xml 文件来观察其中的对应关系,改进原来的代码即可实现转换功能: 123456789101112131415161718192021import requestsimport base64from fontTools.ttLib import TTFontheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}url = 'https://wh.58.com/chuzu/'response = requests.get(url=url, headers=headers)# 匹配 base64 编码的加密字体字符串base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip()# 将 base64 编码的字体字符串解码成二进制编码bin_data = base64.decodebytes(base64_string.encode())# 保存为字体文件with open('58font.woff', 'wb') as f: f.write(bin_data)# 获取字体文件,将其转换为xml文件font = TTFont('58font.woff')font.saveXML('58font.xml') 打开 58font.xml 文件并分析,在 <cmap> 标签内可以看到熟悉的类似于 0x9476、0x958f 的编码,其后四位字符恰好是网页字体的加密编码,可以看到每一个编码后面都对应了一个 glyph 开头的编码 将其与 58font.woff 文件对比,可以看到 code 为 0x958f 这个编码对应的是数字 3,对应的 name 编码是 glyph00004 我们再次获取一个字体文件作为对比分析 依然是 0x958f 这个编码,两次对应的 name 分别是 glyph00004 和 glyph00007,两次对应的数字分别是 3 和 6,那么结论就来了,每次发送请求,code 对应的 name 会随机发生变化,而 name 对应的数字不会发生变化,glyph00001 对应数字 0、glyph00002 对应数字 1,以此类推 那么以 glyph 开头的编码是如何对应相应的数字的呢?在 xml 文件里面,每个编码都有一个 TTGlyph 的标签,标签里面是一行一行的类似于 x,y 坐标的东西,这个其实就是用来绘制字体的,用 matplotlib 根据坐标画个图,就可以看到是一个数字 此时,我们就知道了编码与数字的对应关系,下一步,我们可以查找 xml 文件里,编码对应的 name 的值,也就是以 glyph 开头的编码,然后返回其对应的数字,再替换掉网页源代码里的编码,就能成功获取到我们需要的信息了! 总结一下攻克加密字体的大致思路: 分析网页,找到对应的加密字体文件 如果引用的加密字体是一个 base64 编码的字符串,则需要转换成二进制并保存到 woff 字体文件中 将字体文件转换成 xml 文件 用 FontCreator 软件观察字体文件,结合 xml 文件,分析其编码与真实字体的关系 搞清楚编码与字体的关系后,想办法将编码替换成正常字体 【2x00】思维导图 【3x00】加密字体处理模块【3x01】获取字体文件并转换为xml文件12345678910111213141516def get_font(page_url, page_num): response = requests.get(url=page_url, headers=headers) # 匹配 base64 编码的加密字体字符串 base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip() # print(base64_string) # 将 base64 编码的字体字符串解码成二进制编码 bin_data = base64.decodebytes(base64_string.encode()) # 保存为字体文件 with open('58font.woff', 'wb') as f: f.write(bin_data) print('第' + str(page_num) + '次访问网页,字体文件保存成功!') # 获取字体文件,将其转换为xml文件 font = TTFont('58font.woff') font.saveXML('58font.xml') print('已成功将字体文件转换为xml文件!') return response.text 由主函数传入要发送请求的 url,利用字符串的 split() 方法,匹配 base64 编码的加密字体字符串,利用 base64 模块的 base64.decodebytes() 方法,将 base64 编码的字体字符串解码成二进制编码并保存为字体文件,利用 FontTools 库,将字体文件转换为 xml 文件 【3x02】将加密字体编码与真实字体进行匹配12345678910111213141516171819202122232425262728293031def find_font(): # 以glyph开头的编码对应的数字 glyph_list = { 'glyph00001': '0', 'glyph00002': '1', 'glyph00003': '2', 'glyph00004': '3', 'glyph00005': '4', 'glyph00006': '5', 'glyph00007': '6', 'glyph00008': '7', 'glyph00009': '8', 'glyph00010': '9' } # 十个加密字体编码 unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5'] num_list = [] # 利用xpath语法匹配xml文件内容 font_data = etree.parse('./58font.xml') for unicode in unicode_list: # 依次循环查找xml文件里code对应的name result = font_data.xpath(\"//cmap//map[@code='{}']/@name\".format(unicode))[0] # print(result) # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value for key in glyph_list.keys(): if key == result: num_list.append(glyph_list[key]) print('已成功找到编码所对应的数字!') # print(num_list) # 返回value列表 return num_list 由前面的分析,我们知道 name 的值(即以 glyph 开头的编码)对应的数字是固定的,glyph00001 对应数字 0、glyph00002 对应数字 1,以此类推,所以可以将其构造成为一个字典 glyph_list 同样将十个 code(即类似于 0x9476 的加密字体编码)构造成一个列表 循环查找这十个 code 在 xml 文件里对应的 name 的值,然后将 name 的值与字典文件的 key 值进行对比,如果两者值相同,则获取这个 key 的 value 值,最终得到的列表 num_list,里面的元素就是 unicode_list 列表里面每个加密字体的真实值 【3x03】替换掉网页中所有的加密字体编码12345def replace_font(num, page_response): # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5 result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9]) print('已成功将所有加密字体替换!') return result 传入由上一步 find_font() 函数得到的真实字体的列表,利用 replace() 方法,依次将十个加密字体编码替换掉 【4x00】租房信息提取模块1234567891011121314151617181920212223242526272829303132333435363738def parse_pages(pages): num = 0 soup = BeautifulSoup(pages, 'lxml') # 查找到包含所有租房的li标签 all_house = soup.find_all('li', class_='house-cell') for house in all_house: # 标题 title = house.find('a', class_='strongbox').text.strip() # print(title) # 价格 price = house.find('div', class_='money').text.strip() # print(price) # 户型和面积 layout = house.find('p', class_='room').text.replace(' ', '') # print(layout) # 楼盘和地址 address = house.find('p', class_='infor').text.replace(' ', '').replace('\\n', '') # print(address) # 如果存在经纪人 if house.find('div', class_='jjr'): agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\\n', '') # 如果存在品牌公寓 elif house.find('p', class_='gongyu'): agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\\n', '') # 如果存在个人房源 else: agent = house.find('p', class_='geren').text.replace(' ', '').replace('\\n', '') # print(agent) data = [title, price, layout, address, agent] save_to_mysql(data) num += 1 print('第' + str(num) + '条数据爬取完毕,暂停3秒!') time.sleep(3) 利用 BeautifulSoup 解析库很容易提取到相关信息,这里要注意的是,租房信息来源分为三种:经纪人、品牌公寓和个人房源,这三个的元素节点也不一样,因此匹配的时候要注意 【5x00】MySQL数据储存模块【5x01】创建MySQL数据库的表123456def create_mysql_table(): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)' cursor.execute(sql) db.close() 首先指定数据库为 58tc_spiders,需要事先使用 MySQL 语句创建,也可以通过 MySQL Workbench 手动创建 然后使用 SQL 语句创建 一个表:58tc_data,表中包含 title、price、layout、address、agent 五个字段,类型都为 varchar 此创建表的操作也可以事先手动创建,手动创建后就不需要此函数了 【5x02】将数据储存到MySQL数据库12345678910def save_to_mysql(data): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)' try: cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4])) db.commit() except: db.rollback() db.close() commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏 【6x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: 58tongcheng.py# @Software: PyCharm# =============================================import requestsimport timeimport randomimport base64import pymysqlfrom lxml import etreefrom bs4 import BeautifulSoupfrom fontTools.ttLib import TTFontheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}# 获取字体文件并转换为xml文件def get_font(page_url, page_num): response = requests.get(url=page_url, headers=headers) # 匹配 base64 编码的加密字体字符串 base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip() # print(base64_string) # 将 base64 编码的字体字符串解码成二进制编码 bin_data = base64.decodebytes(base64_string.encode()) # 保存为字体文件 with open('58font.woff', 'wb') as f: f.write(bin_data) print('第' + str(page_num) + '次访问网页,字体文件保存成功!') # 获取字体文件,将其转换为xml文件 font = TTFont('58font.woff') font.saveXML('58font.xml') print('已成功将字体文件转换为xml文件!') return response.text# 将加密字体编码与真实字体进行匹配def find_font(): # 以glyph开头的编码对应的数字 glyph_list = { 'glyph00001': '0', 'glyph00002': '1', 'glyph00003': '2', 'glyph00004': '3', 'glyph00005': '4', 'glyph00006': '5', 'glyph00007': '6', 'glyph00008': '7', 'glyph00009': '8', 'glyph00010': '9' } # 十个加密字体编码 unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5'] num_list = [] # 利用xpath语法匹配xml文件内容 font_data = etree.parse('./58font.xml') for unicode in unicode_list: # 依次循环查找xml文件里code对应的name result = font_data.xpath(\"//cmap//map[@code='{}']/@name\".format(unicode))[0] # print(result) # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value for key in glyph_list.keys(): if key == result: num_list.append(glyph_list[key]) print('已成功找到编码所对应的数字!') # print(num_list) # 返回value列表 return num_list# 替换掉网页中所有的加密字体编码def replace_font(num, page_response): # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5 result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9]) print('已成功将所有加密字体替换!') return result# 提取租房信息def parse_pages(pages): num = 0 soup = BeautifulSoup(pages, 'lxml') # 查找到包含所有租房的li标签 all_house = soup.find_all('li', class_='house-cell') for house in all_house: # 标题 title = house.find('a', class_='strongbox').text.strip() # print(title) # 价格 price = house.find('div', class_='money').text.strip() # print(price) # 户型和面积 layout = house.find('p', class_='room').text.replace(' ', '') # print(layout) # 楼盘和地址 address = house.find('p', class_='infor').text.replace(' ', '').replace('\\n', '') # print(address) # 如果存在经纪人 if house.find('div', class_='jjr'): agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\\n', '') # 如果存在品牌公寓 elif house.find('p', class_='gongyu'): agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\\n', '') # 如果存在个人房源 else: agent = house.find('p', class_='geren').text.replace(' ', '').replace('\\n', '') # print(agent) data = [title, price, layout, address, agent] save_to_mysql(data) num += 1 print('第' + str(num) + '条数据爬取完毕,暂停3秒!') time.sleep(3)# 创建MySQL数据库的表:58tc_datadef create_mysql_table(): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)' cursor.execute(sql) db.close()# 将数据储存到MySQL数据库def save_to_mysql(data): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)' try: cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4])) db.commit() except: db.rollback() db.close()if __name__ == '__main__': create_mysql_table() print('MySQL表58tc_data创建成功!') for i in range(1, 71): url = 'https://wh.58.com/chuzu/pn' + str(i) + '/' response = get_font(url, i) num_list = find_font() pro_pages = replace_font(num_list, response) parse_pages(pro_pages) print('第' + str(i) + '页数据爬取完毕!') time.sleep(random.randint(3, 60)) print('所有数据爬取完毕!') 【7x00】数据截图","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"58同城","slug":"58同城","permalink":"https://www.itrhx.com/tags/58同城/"}]},{"title":"Python3 爬虫实战 — 模拟登陆12306【点触验证码对抗】","slug":"A57-pyspider-12306-login","date":"2019-10-21T08:41:50.349Z","updated":"2019-10-21T13:33:47.617Z","comments":true,"path":"2019/10/21/A57-pyspider-12306-login/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A57-pyspider-12306-login/","excerpt":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://kyfw.12306.cn/otn/resources/login.html实现目标:模拟登陆中国铁路12306,攻克点触验证码涉及知识:点触验证码的攻克、自动化测试工具 Selenium 的使用、对接在线打码平台完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/12306-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://kyfw.12306.cn/otn/resources/login.html实现目标:模拟登陆中国铁路12306,攻克点触验证码涉及知识:点触验证码的攻克、自动化测试工具 Selenium 的使用、对接在线打码平台完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/12306-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】思维导图 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证 发送请求,出现验证码后,剪裁并保存验证码图片 选择在线打码平台,获取其API,以字节流格式发送图片 打码平台人工识别验证码,返回验证码的坐标信息 解析返回的坐标信息,模拟点击验证码,完成验证后点击登陆 【2x00】打码平台选择关于打码平台:在线打码平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多,本次实战使用超级鹰打码平台 使用打码平台:在超级鹰打码平台注册账号,官网:http://www.chaojiying.com/ ,充值一块钱得到 1000 题分,在用户中心里面申请一个软件 ID ,在价格体系里面确定验证码的类型,先观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1 至 4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 然后在开发文档里面获取其 Python API,下载下来以备后用 【3x00】初始化模块【3x01】初始化函数1234567891011121314151617181920212223242526# 12306账号密码USERNAME = '155********'PASSWORD = '***********'# 超级鹰打码平台账号密码CHAOJIYING_USERNAME = '*******'CHAOJIYING_PASSWORD = '*******'# 超级鹰打码平台软件IDCHAOJIYING_SOFT_ID = '********'# 验证码类型CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.username = USERNAME self.password = PASSWORD self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) 定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录页面 url ,谷歌浏览器驱动的目录(path),浏览器启动参数等,将超级鹰账号密码等相关参数传递给超级鹰 API 【3x02】账号密码输入函数12345678910111213def get_input_element(self): # 登录页面发送请求 self.browser.get(self.url) # 登录页面默认是扫码登录,所以首先要点击账号登录 login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) # 查找到账号密码输入位置的元素 username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) # 输入账号密码 username.send_keys(self.username) password.send_keys(self.password) 分析页面可知,登陆页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userName 和 J-password,调用 send_keys() 方法输入账号密码 【4x00】验证码处理模块1234567891011121314151617181920212223242526def crack(self): # 调用账号密码输入函数 self.get_input_element() # 调用验证码图片剪裁函数 image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) # 调用验证码坐标解析函数 locations = self.get_points(result) # 调用模拟点击验证码函数 self.touch_click_words(locations) # 调用模拟点击登录函数 self.login() try: # 查找是否出现用户的姓名,若出现表示登录成功 success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print('用户' + cc.text + '登录成功') # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值 except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() crack() 为验证码处理模块的主函数 调用账号密码输入函数 get_input_element(),等待账号密码输入完毕 调用验证码图片剪裁函数 get_touclick_image(),得到验证码图片 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON,如果识别成功,典型的返回结果类似于: 12{'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': '1f8e1d4bef8b11484cb1f1f34299865b'} 其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔 调用 get_points() 函数解析超级鹰识别结果 调用 touch_click_words() 函数对符合要求的图片进行点击 调用模拟点击登录函数 login(),点击登陆按钮模拟登陆 使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值 【4x01】验证码图片剪裁函数1234567891011121314def get_touclick_image(self, name='12306.png'): # 获取验证码的位置 element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] # 先对整个页面截图 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) # 根据验证码坐标信息,剪裁出验证码图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha 首先查找到验证码的坐标信息,先对整个页面截图,然后根据验证码坐标信息,剪裁出验证码图片 location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 【4x02】验证码坐标解析函数123456def get_points(self, captcha_result): # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔 groups = captcha_result.get('pic_str').split('|') # 将坐标信息变成列表的形式 locations = [[int(number) for number in group.split(',')] for group in groups] return locations get_points() 方法将超级鹰的验证码识别结果变成列表的形式 【4x03】模拟点击验证码函数123456def touch_click_words(self, locations): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) # 循环点击正确验证码的坐标 for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform() 循环提取正确的验证码坐标信息,依次点击验证码 【5x00】登录模块123def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click() 分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录 【6x00】完整代码【6x01】12306.py123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: 12306.py# @Software: PyCharm# =============================================import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import ChaojiyingClientfrom selenium.common.exceptions import TimeoutException# 12306账号密码USERNAME = '155********'PASSWORD = '***********'# 超级鹰打码平台账号密码CHAOJIYING_USERNAME = '********'CHAOJIYING_PASSWORD = '********'# 超级鹰打码平台软件IDCHAOJIYING_SOFT_ID = '******'# 验证码类型CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.username = USERNAME self.password = PASSWORD self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) def crack(self): # 调用账号密码输入函数 self.get_input_element() # 调用验证码图片剪裁函数 image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) # 调用验证码坐标解析函数 locations = self.get_points(result) # 调用模拟点击验证码函数 self.touch_click_words(locations) # 调用模拟点击登录函数 self.login() try: # 查找是否出现用户的姓名,若出现表示登录成功 success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print('用户' + cc.text + '登录成功') # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值 except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() # 账号密码输入函数 def get_input_element(self): # 登录页面发送请求 self.browser.get(self.url) # 登录页面默认是扫码登录,所以首先要点击账号登录 login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) # 查找到账号密码输入位置的元素 username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) # 输入账号密码 username.send_keys(self.username) password.send_keys(self.password) # 验证码图片剪裁函数 def get_touclick_image(self, name='12306.png'): # 获取验证码的位置 element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] # 先对整个页面截图 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) # 根据验证码坐标信息,剪裁出验证码图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha # 验证码坐标解析函数,分析超级鹰返回的坐标 def get_points(self, captcha_result): # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔 groups = captcha_result.get('pic_str').split('|') # 将坐标信息变成列表的形式 locations = [[int(number) for number in group.split(',')] for group in groups] return locations # 模拟点击验证码函数 def touch_click_words(self, locations): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) # 循环点击正确验证码的坐标 for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform() # 模拟点击登录函数 def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click()if __name__ == '__main__': crack = CrackTouClick() crack.crack() 【6x02】chaojiying.py12345678910111213141516171819202122232425262728293031323334353637383940414243import requestsfrom hashlib import md5class ChaojiyingClient(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): \"\"\" im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html \"\"\" params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): \"\"\" im_id:报错题目的图片ID \"\"\" params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json() 【7x00】效果实现动图最终实现效果图:(关键信息已经过打码处理)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"12306","slug":"12306","permalink":"https://www.itrhx.com/tags/12306/"}]},{"title":"Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】","slug":"A56-pyspider-bilibili-login","date":"2019-10-21T04:26:46.838Z","updated":"2019-10-21T13:33:57.637Z","comments":true,"path":"2019/10/21/A56-pyspider-bilibili-login/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A56-pyspider-bilibili-login/","excerpt":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://passport.bilibili.com/login实现目标:模拟登陆哔哩哔哩,攻克滑动验证码涉及知识:滑动验证码的攻克、自动化测试工具 Selenium 的使用完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/bilibili-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://passport.bilibili.com/login实现目标:模拟登陆哔哩哔哩,攻克滑动验证码涉及知识:滑动验证码的攻克、自动化测试工具 Selenium 的使用完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/bilibili-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】思维导图 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证 分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片 对比原始的图片和带缺口的图片的像素,像素不同的地方就是缺口位置 计算出滑块缺口的位置,得到所需要滑动的距离 拖拽时要模仿人的行为,由于有个对准过程,所以要构造先快后慢的运动轨迹 最后利用 Selenium 进行对滑块的拖拽 【2x00】登陆模块【2x01】初始化函数12345678910111213def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) # 你的哔哩哔哩用户名 username = '155********' # 你的哔哩哔哩登陆密码 password = '***********' wait = WebDriverWait(browser, 20) global 关键字定义了发起请求的url、用户名、密码等全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时 【2x02】登陆函数12345678910111213141516def login(): browser.get(url) # 获取用户名输入框 user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) # 获取密码输入框 passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) # 输入用户名 user.send_keys(username) # 输入密码 passwd.send_keys(password) # 获取登录按钮 login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) # 随机暂停几秒 time.sleep(random.random() * 3) # 点击登陆按钮 login_btn.click() 等待用户名输入框和密码输入框对应的 ID 节点加载出来 获取这两个节点,用户名输入框 id="login-username",密码输入框 id="login-passwd" 调用 send_keys() 方法输入用户名和密码 获取登录按钮 class="btn btn-login" 随机产生一个数并将其扩大三倍作为暂停时间 最后调用 click() 方法实现登录按钮的点击 【3x00】验证码处理模块【3x01】验证码元素查找函数12345678910111213141516171819202122def find_element(): # 获取带有缺口的图片 c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) # 获取需要滑动的图片 c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) # 获取完整的图片 c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) # 隐藏需要滑动的图片 hide_element(c_slice) # 保存带有缺口的图片 save_screenshot(c_background, 'back') # 显示需要滑动的图片 show_element(c_slice) # 保存需要滑动的图片 save_screenshot(c_slice, 'slice') # 显示完整的图片 show_element(c_full_bg) # 保存完整的图片 save_screenshot(c_full_bg, 'full') 获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片 分析页面代码,三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片 定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute 最后传值给 save_screenshot() 函数,进一步对验证码进行处理 【3x02】元素可见性设置函数12345678# 设置元素不可见def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")# 设置元素可见def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\") 【3x03】验证码截图函数123456789101112131415161718192021222324def save_screenshot(obj, name): try: # 首先对出现验证码后的整个页面进行截图保存 pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) # 计算传入的obj,也就是三张图片的位置信息 left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] # 打印输出一下每一张图的位置信息 print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存 im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增 size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 首先调用 save_screenshot() 属性对整个页面截图并保存 然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存 【4x00】验证码滑动模块【4x01】滑动主函数123456def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3) 向 get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证 【4x02】缺口位置寻找函数123456789101112def is_pixel_equal(bg_image, fullbg_image, x, y): # 获取两张图片对应像素点的RGB数据 bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] # 设定一个阈值 threshold = 60 # 比较两张图 RGB 的绝对值是否均小于定义的阈值 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False 将完整图片和缺口图片两个对象分别赋值给变量 bg_image 和 fullbg_image,接下来对比图片获取缺口。遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置 【4x03】计算滑块移动距离函数123456789def get_distance(bg_image, fullbg_image): # 滑块的初始位置 distance = 60 # 遍历两张图片的每个像素 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): # 调用缺口位置寻找函数 if not is_pixel_equal(fullbg_image, bg_image, i, j): return i get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 缺口位置寻找函数判断两张图片同一位置的像素是否相同,若不相同则返回该点的值 【4x04】构造移动轨迹函数1234567891011121314151617181920def get_trace(distance): trace = [] # 设置加速距离为总距离的4/5 faster_distance = distance * (4 / 5) # 设置初始位置、初始速度、时间间隔 start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 10 else: a = -10 # 位移 move = v0 * t + 1 / 2 * a * t * t # 当前时刻的速度 v = v0 + a * t v0 = v start += move trace.append(round(move)) # trace 记录了每个时间间隔移动了多少位移 return trace get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系: 12move = v0 * t + 0.5 * a * t * t v = v0 + a * t 设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为10和-10,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了 【4x05】模拟拖动函数123456789101112def move_to_gap(trace): # 获取滑动按钮 slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) # 点击并拖动滑块 ActionChains(browser).click_and_hold(slider).perform() # 遍历运动轨迹获取每小段位移距离 for x in trace: # 移动此位移 ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) # 释放鼠标 ActionChains(browser).release().perform() 传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: bilibili.py# @Software: PyCharm# =============================================from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport randomfrom PIL import Image# 初始化函数def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) # 你的哔哩哔哩用户名 username = '155********' # 你的哔哩哔哩登录密码 password = '***********' wait = WebDriverWait(browser, 20)# 登录函数def login(): browser.get(url) # 获取用户名输入框 user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) # 获取密码输入框 passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) # 输入用户名 user.send_keys(username) # 输入密码 passwd.send_keys(password) # 获取登录按钮 login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) # 随机暂停几秒 time.sleep(random.random() * 3) # 点击登陆按钮 login_btn.click()# 验证码元素查找函数def find_element(): # 获取带有缺口的图片 c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) # 获取需要滑动的图片 c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) # 获取完整的图片 c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) # 隐藏需要滑动的图片 hide_element(c_slice) # 保存带有缺口的图片 save_screenshot(c_background, 'back') # 显示需要滑动的图片 show_element(c_slice) # 保存需要滑动的图片 save_screenshot(c_slice, 'slice') # 显示完整的图片 show_element(c_full_bg) # 保存完整的图片 save_screenshot(c_full_bg, 'full')# 设置元素不可见def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")# 设置元素可见def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\")# 验证码截图函数def save_screenshot(obj, name): try: # 首先对出现验证码后的整个页面进行截图保存 pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) # 计算传入的obj,也就是三张图片的位置信息 left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] # 打印输出一下每一张图的位置信息 print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存 im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg)# 滑动模块的主函数def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3)# 计算滑块移动距离函数def get_distance(bg_image, fullbg_image): # 滑块的初始位置 distance = 60 # 遍历两张图片的每个像素 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): # 调用缺口位置寻找函数 if not is_pixel_equal(fullbg_image, bg_image, i, j): return i# 缺口位置寻找函数def is_pixel_equal(bg_image, fullbg_image, x, y): # 获取两张图片对应像素点的RGB数据 bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] # 设定一个阈值 threshold = 60 # 比较两张图 RGB 的绝对值是否均小于定义的阈值 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False# 构造移动轨迹函数def get_trace(distance): trace = [] # 设置加速距离为总距离的4/5 faster_distance = distance * (4 / 5) # 设置初始位置、初始速度、时间间隔 start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 10 else: a = -10 # 位移 move = v0 * t + 1 / 2 * a * t * t # 当前时刻的速度 v = v0 + a * t v0 = v start += move trace.append(round(move)) # trace 记录了每个时间间隔移动了多少位移 return trace# 模拟拖动函数def move_to_gap(trace): # 获取滑动按钮 slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) # 点击并拖动滑块 ActionChains(browser).click_and_hold(slider).perform() # 遍历运动轨迹获取每小段位移距离 for x in trace: # 移动此位移 ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) # 释放鼠标 ActionChains(browser).release().perform()if __name__ == '__main__': init() login() find_element() slide() 【6x00】效果实现动图最终实现效果图:(关键信息已经过打码处理)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"哔哩哔哩","slug":"哔哩哔哩","permalink":"https://www.itrhx.com/tags/哔哩哔哩/"}]},{"title":"Python3 爬虫实战 — 虎扑论坛步行街","slug":"A55-pyspider-hupu","date":"2019-10-12T15:28:23.380Z","updated":"2019-10-21T04:08:46.598Z","comments":true,"path":"2019/10/12/A55-pyspider-hupu/","link":"","permalink":"https://www.itrhx.com/2019/10/12/A55-pyspider-hupu/","excerpt":"爬取时间:2019-10-12爬取难度:★★☆☆☆☆请求链接:https://bbs.hupu.com/bxj爬取目标:爬取虎扑论坛步行街的帖子,包含主题,作者,发布时间等,数据保存到 MongoDB 数据库涉及知识:请求库 requests、解析库 Beautiful Soup、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/hupu其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-12爬取难度:★★☆☆☆☆请求链接:https://bbs.hupu.com/bxj爬取目标:爬取虎扑论坛步行街的帖子,包含主题,作者,发布时间等,数据保存到 MongoDB 数据库涉及知识:请求库 requests、解析库 Beautiful Soup、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/hupu其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察虎扑论坛步行街分区,请求地址为:https://bbs.hupu.com/bxj 第一页:https://bbs.hupu.com/bxj 第二页:https://bbs.hupu.com/bxj-2 第三页:https://bbs.hupu.com/bxj-3 不难发现,每增加一页,只需要添加 -页数 参数即可,最后一页是第 50 页,因此可以利用 for 循环依次爬取,定义一个 get_pages() 函数,返回初始化 Beautiful Soup 的对象 page_soup,方便后面的解析函数调用 虽然一共有 50 页,但是当用户访问第 10 页以后的页面的时候,会要求登录虎扑,不然就没法查看,而且登录时会出现智能验证,所以程序只爬取前 10 页的数据 123456789101112def get_pages(page_url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } response = requests.get(url=page_url, headers=headers) page_soup = BeautifulSoup(response.text, 'lxml') return page_soupif __name__ == '__main__': for i in range(1, 11): url = 'https://bbs.hupu.com/bxj-' + str(i) soup = get_pages(url) 【2x00】解析模块使用 Beautiful Soup 对网页各个信息进行提取,最后将这些信息放进一个列表里,然后调用列表的 .append() 方法,再将每条帖子的列表依次加到另一个新列表里,最终返回的是类似于如下形式的列表: 1[['帖子1', '作者1'], ['帖子2', '作者2'], ['帖子3', '作者3']] 这样做的目的是:方便 MongoDB 依次储存每一条帖子的信息 123456789101112131415161718192021222324252627282930313233343536373839def parse_pages(page_soup): data_list = [] all_list = page_soup.find('ul', class_='for-list') post_list = all_list.find_all('li') # print(result_list) for post in post_list: # 帖子名称 post_title = post.find('a', class_='truetit').text # print(post_title) # 帖子链接 post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href'] # print(post_url) # 作者 author = post.select('.author > a')[0].text # print(author) # 作者主页 author_url = post.select('.author > a')[0]['href'] # print(author_url) # 发布日期 post_date = post.select('.author > a')[1].text # print(post_date) reply_view = post.find('span', class_='ansour').text # 回复数 post_reply = reply_view.split('/')[0].strip() # print(post_reply) # 浏览量 post_view = reply_view.split('/')[1].strip() # print(post_view) # 最后回复时间 last_data = post.select('.endreply > a')[0].text # print(last_data) # 最后回复用户 last_user = post.select('.endreply > span')[0].text # print(last_user) data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user]) # print(data_list) return data_list 【3x00】MongoDB 数据储存模块首先使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 hupu,集合为 bxj 将解析函数返回的列表传入到储存函数,依次循环该列表,对每一条帖子的信息进行提取并储存 1234567891011121314151617def mongodb(data_list): client = MongoClient('localhost', 27017) db = client.hupu collection = db.bxj for data in data_list: bxj = { '帖子名称': data[0], '帖子链接': data[1], '作者': data[2], '作者主页': data[3], '发布日期': str(data[4]), '回复数': data[5], '浏览量': data[6], '最后回复时间': str(data[7]), '最后回复用户': data[8] } collection.insert_one(bxj) 【4x00】完整代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-12# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: hupu.py# @Software: PyCharm# =============================================import requestsimport timeimport randomfrom pymongo import MongoClientfrom bs4 import BeautifulSoupdef get_pages(page_url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } response = requests.get(url=page_url, headers=headers) page_soup = BeautifulSoup(response.text, 'lxml') return page_soupdef parse_pages(page_soup): data_list = [] all_list = page_soup.find('ul', class_='for-list') post_list = all_list.find_all('li') # print(result_list) for post in post_list: # 帖子名称 post_title = post.find('a', class_='truetit').text # print(post_title) # 帖子链接 post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href'] # print(post_url) # 作者 author = post.select('.author > a')[0].text # print(author) # 作者主页 author_url = post.select('.author > a')[0]['href'] # print(author_url) # 发布日期 post_date = post.select('.author > a')[1].text # print(post_date) reply_view = post.find('span', class_='ansour').text # 回复数 post_reply = reply_view.split('/')[0].strip() # print(post_reply) # 浏览量 post_view = reply_view.split('/')[1].strip() # print(post_view) # 最后回复时间 last_data = post.select('.endreply > a')[0].text # print(last_data) # 最后回复用户 last_user = post.select('.endreply > span')[0].text # print(last_user) data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user]) # print(data_list) return data_listdef mongodb(data_list): client = MongoClient('localhost', 27017) db = client.hupu collection = db.bxj for data in data_list: bxj = { '帖子名称': data[0], '帖子链接': data[1], '作者': data[2], '作者主页': data[3], '发布日期': str(data[4]), '回复数': data[5], '浏览量': data[6], '最后回复时间': str(data[7]), '最后回复用户': data[8] } collection.insert_one(bxj)if __name__ == '__main__': for i in range(1, 11): url = 'https://bbs.hupu.com/bxj-' + str(i) soup = get_pages(url) result_list = parse_pages(soup) mongodb(result_list) print('第', i, '页数据爬取完毕!') time.sleep(random.randint(3, 10)) print('前10页所有数据爬取完毕!') 【5x00】数据截图一共爬取到 1180 条数据: 【6x00】程序不足的地方程序只能爬取前 10 页的数据,因为虎扑论坛要求从第 11 页开始,必须登录账号才能查看,并且登录时会有智能验证,可以使用自动化测试工具 Selenium 模拟登录账号后再进行爬取。","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"虎扑论坛","slug":"虎扑论坛","permalink":"https://www.itrhx.com/tags/虎扑论坛/"}]},{"title":"Python3 爬虫实战 — 安居客武汉二手房","slug":"A54-pyspider-anjuke","date":"2019-10-09T15:02:42.994Z","updated":"2019-10-21T04:05:48.158Z","comments":true,"path":"2019/10/09/A54-pyspider-anjuke/","link":"","permalink":"https://www.itrhx.com/2019/10/09/A54-pyspider-anjuke/","excerpt":"爬取时间:2019-10-09爬取难度:★★☆☆☆☆请求链接:https://wuhan.anjuke.com/sale/爬取目标:爬取武汉二手房每一条售房信息,包含地理位置、价格、面积等,保存为 CSV 文件涉及知识:请求库 requests、解析库 Beautiful Soup、CSV 文件储存、列表操作、分页判断完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/anjuke其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-09爬取难度:★★☆☆☆☆请求链接:https://wuhan.anjuke.com/sale/爬取目标:爬取武汉二手房每一条售房信息,包含地理位置、价格、面积等,保存为 CSV 文件涉及知识:请求库 requests、解析库 Beautiful Soup、CSV 文件储存、列表操作、分页判断完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/anjuke其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】页面整体分析分析 安居客武汉二手房页面,这次爬取实战准备使用 BeautifulSoup 解析库,熟练 BeautifulSoup 解析库的用法,注意到该页面与其他页面不同的是,不能一次性看到到底有多少页,以前知道一共有多少页,直接一个循环爬取就行了,虽然可以通过改变 url 来尝试找到最后一页,但是这样就显得不程序员了😂,因此可以通过 BeautifulSoup 解析 下一页按钮,提取到下一页的 url,直到没有 下一页按钮 这个元素为止,从而实现所有页面的爬取,剩下的信息提取和储存就比较简单了 【2x00】解析模块分析页面,可以发现每条二手房信息都是包含在 <li> 标签内的,因此可以使用 BeautifulSoup 解析页面得到所有的 <li> 标签,然后再循环访问每个 <li> 标签,依次解析得到每条二手房的各种信息 1234567891011121314151617181920212223242526272829303132333435363738394041def parse_pages(url, num): response = requests.get(url=url, headers=headers) soup = BeautifulSoup(response.text, 'lxml') result_list = soup.find_all('li', class_='list-item') # print(len(result_list)) for result in result_list: # 标题 title = result.find('a', class_='houseListTitle').text.strip() # print(title) # 户型 layout = result.select('.details-item > span')[0].text # print(layout) # 面积 cover = result.select('.details-item > span')[1].text # print(cover) # 楼层 floor = result.select('.details-item > span')[2].text # print(floor) # 建造年份 year = result.select('.details-item > span')[3].text # print(year) # 单价 unit_price = result.find('span', class_='unit-price').text.strip() # print(unit_price) # 总价 total_price = result.find('span', class_='price-det').text.strip() # print(total_price) # 关键字 keyword = result.find('div', class_='tags-bottom').text.strip() # print(keyword) # 地址 address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\\n', '') # print(address) # 详情页url details_url = result.find('a', class_='houseListTitle')['href'] # print(details_url)if __name__ == '__main__': start_num = 0 start_url = 'https://wuhan.anjuke.com/sale/' parse_pages(start_url, start_num) 【3x00】循环爬取模块前面已经分析过,该网页是无法一下就能看到一共有多少页的,尝试找到最后一页,发现一共有50页,那么此时就可以搞个循环,一直到第50页就行了,但是如果有一天页面数增加了呢,那么代码的可维护性就不好了,我们可以观察 下一页按钮 ,当存在下一页的时候,是 <a> 标签,并且带有下一页的 URL,不存在下一页的时候是 <i> 标签,因此可以写个 if 语句,判断是否存在此 <a> 标签,若存在,表示有下一页,然后提取其 href 属性并传给解析模块,实现后面所有页面的信息提取,此外,由于安居客有反爬系统,我们还可以利用 Python中的 random.randint() 方法,在两个数值之间随机取一个数,传入 time.sleep() 方法,实现随机暂停爬取 12345678910# 判断是否还有下一页next_url = soup.find_all('a', class_='aNxt')if len(next_url) != 0: num += 1 print('第' + str(num) + '页数据爬取完毕!') # 3-60秒之间随机暂停 time.sleep(random.randint(3, 60)) parse_pages(next_url[0].attrs['href'], num)else: print('所有数据爬取完毕!') 【4x00】数据储存模块数据储存比较简单,将每个二手房信息组成一个列表,依次写入到 anjuke.csv 文件中即可 1234results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url]with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f: w = csv.writer(f) w.writerow(results) 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-09# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: anjuke.py# @Software: PyCharm# =============================================import requestsimport timeimport csvimport randomfrom bs4 import BeautifulSoupheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def parse_pages(url, num): response = requests.get(url=url, headers=headers) soup = BeautifulSoup(response.text, 'lxml') result_list = soup.find_all('li', class_='list-item') # print(len(result_list)) for result in result_list: # 标题 title = result.find('a', class_='houseListTitle').text.strip() # print(title) # 户型 layout = result.select('.details-item > span')[0].text # print(layout) # 面积 cover = result.select('.details-item > span')[1].text # print(cover) # 楼层 floor = result.select('.details-item > span')[2].text # print(floor) # 建造年份 year = result.select('.details-item > span')[3].text # print(year) # 单价 unit_price = result.find('span', class_='unit-price').text.strip() # print(unit_price) # 总价 total_price = result.find('span', class_='price-det').text.strip() # print(total_price) # 关键字 keyword = result.find('div', class_='tags-bottom').text.strip() # print(keyword) # 地址 address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\\n', '') # print(address) # 详情页url details_url = result.find('a', class_='houseListTitle')['href'] # print(details_url) results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url] with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f: w = csv.writer(f) w.writerow(results) # 判断是否还有下一页 next_url = soup.find_all('a', class_='aNxt') if len(next_url) != 0: num += 1 print('第' + str(num) + '页数据爬取完毕!') # 3-60秒之间随机暂停 time.sleep(random.randint(3, 60)) parse_pages(next_url[0].attrs['href'], num) else: print('所有数据爬取完毕!')if __name__ == '__main__': with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as fp: writer = csv.writer(fp) writer.writerow(['标题', '户型', '面积', '楼层', '建造年份', '单价', '总价', '关键字', '地址', '详情页地址']) start_num = 0 start_url = 'https://wuhan.anjuke.com/sale/' parse_pages(start_url, start_num) 【6x00】数据截图 【7x00】程序不足的地方 虽然使用了随机暂停爬取的方法,但是在爬取了大约 20 页的数据后依然会出现验证页面,导致程序终止 原来设想的是可以由用户手动输入城市的拼音来查询不同城市的信息,方法是把用户输入的城市拼音和其他参数一起构造成一个 URL,然后对该 URL 发送请求,判断请求返回的代码,如果是 200 就代表可以访问,也就是用户输入的城市是正确的,然而发现即便是输入错误,该 URL 依然可以访问,只不过会跳转到一个正确的页面,没有搞清楚是什么原理,也就无法实现由用户输入城市来查询这个功能","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"安居客","slug":"安居客","permalink":"https://www.itrhx.com/tags/安居客/"}]},{"title":"使用 Hexo-Git-Backup 插件备份你的 Hexo 博客","slug":"A53-hexo-backup","date":"2019-09-29T10:02:15.603Z","updated":"2019-12-29T07:19:49.434Z","comments":true,"path":"2019/09/29/A53-hexo-backup/","link":"","permalink":"https://www.itrhx.com/2019/09/29/A53-hexo-backup/","excerpt":"","text":"欢迎关注我的 CSDN 专栏:《个人博客搭建:Hexo+Github Pages》,从搭建到美化一条龙,帮你解决 Hexo 常见问题! 由于 Hexo 博客是静态托管的,所有的原始数据都保存在本地,如果哪一天电脑坏了,或者是误删了本地数据,那就是叫天天不应叫地地不灵了,此时定时备份就显得比较重要了,常见的备份方法有:打包数据保存到U盘、云盘或者其他地方,但是早就有大神开发了备份插件:hexo-git-backup ,只需要一个命令就可以将所有数据包括主题文件备份到 github 了 首先进入你博客目录,输入命令 hexo version 查看 Hexo 版本,如图所示,我的版本是 3.7.1: 安装备份插件,如果你的 Hexo 版本是 2.x.x,则使用以下命令安装: 1$ npm install hexo-git-backup@0.0.91 --save 如果你的 Hexo 版本是 3.x.x,则使用以下命令安装: 1$ npm install hexo-git-backup --save 到 Hexo 博客根目录的 _config.yml 配置文件里添加以下配置: 1234567backup: type: git theme: material-x-1.2.1 message: Back up my www.itrhx.com blog repository: github: git@github.com:TRHX/TRHX.github.io.git,backup coding: git@git.dev.tencent.com:TRHX/TRHX.git,backup 参数解释: theme:你要备份的主题名称 message:自定义提交信息 repository:仓库名,注意仓库地址后面要添加一个分支名,比如我就创建了一个 backup 分支 最后使用以下命令备份你的博客: 1$ hexo backup 或者使用以下简写命令也可以: 1$ hexo b 备份成功后可以在你的仓库分支下看到备份的原始文件:","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"备份","slug":"备份","permalink":"https://www.itrhx.com/tags/备份/"}]},{"title":"Python3 爬虫实战 — 豆瓣电影TOP250","slug":"A52-pyspider-doubantop250","date":"2019-09-28T08:35:19.823Z","updated":"2019-10-21T04:01:29.248Z","comments":true,"path":"2019/09/28/A52-pyspider-doubantop250/","link":"","permalink":"https://www.itrhx.com/2019/09/28/A52-pyspider-doubantop250/","excerpt":"爬取时间:2019-09-27爬取难度:★★☆☆☆☆请求链接:https://movie.douban.com/top250 以及每部电影详情页爬取目标:爬取榜单上每一部电影详情页的数据,保存为 CSV 文件;下载所有电影海报到本地涉及知识:请求库 requests、解析库 lxml、Xpath 语法、正则表达式、CSV 和二进制数据储存、列表操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/douban-top250其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-09-27爬取难度:★★☆☆☆☆请求链接:https://movie.douban.com/top250 以及每部电影详情页爬取目标:爬取榜单上每一部电影详情页的数据,保存为 CSV 文件;下载所有电影海报到本地涉及知识:请求库 requests、解析库 lxml、Xpath 语法、正则表达式、CSV 和二进制数据储存、列表操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/douban-top250其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察豆瓣电影 Top 250,请求地址为:https://movie.douban.com/top250 每页展示25条电影信息,照例翻页观察 url 的变化: 第一页:https://movie.douban.com/top250 第二页:https://movie.douban.com/top250?start=25&filter= 第三页:https://movie.douban.com/top250?start=50&filter= 一共有10页,每次改变的是 start 的值,利用一个 for 循环,从 0 到 250 每隔 25 取一个值拼接到 url,实现循环爬取每一页,由于我们的目标是进入每一部电影的详情页,然后爬取详情页的内容,所以我们可以使用 Xpath 提取每一页每部电影详情页的 URL,将其赋值给 m_urls,并返回 m_urls,m_urls 是一个列表,列表元素就是电影详情页的 URL 12345678910def index_pages(number): url = 'https://movie.douban.com/top250?start=%s&filter=' % number index_response = requests.get(url=url, headers=headers) tree = etree.HTML(index_response.text) m_urls = tree.xpath(\"//li/div/div/a/@href\") return m_urlsif __name__ == '__main__': for i in range(0, 250, 25): movie_urls = index_pages(i) 【2x00】解析模块定义一个解析函数 parse_pages(),利用 for 循环,依次提取 index_pages() 函数返回的列表中的元素,也就是每部电影详情页的 URL,将其传给解析函数进行解析 1234567891011def index_pages(number): expressionsdef parse_pages(url): expressionsif __name__ == '__main__': for i in range(0, 250, 25): movie_urls = index_pages(i) for movie_url in movie_urls: results = parse_pages(movie_url) 详细看一下解析函数 parse_pages(),首先要对接收到的详情页 URL 发送请求,获取响应内容,然后再使用 Xpath 提取相关信息 123def parse_pages(url): movie_pages = requests.get(url=url, headers=headers) parse_movie = etree.HTML(movie_pages.text) 【2x01】Xpath 解析排名、电影名、评分信息其中排名、电影名和评分信息是最容易匹配到的,直接使用 Xpath 语法就可以轻松解决: 12345678# 排名ranking = parse_movie.xpath(\"//span[@class='top250-no']/text()\")# 电影名name = parse_movie.xpath(\"//h1/span[1]/text()\")# 评分score = parse_movie.xpath(\"//div[@class='rating_self clearfix']/strong/text()\") 【2x02】Xpath 解析参评人数接下来准备爬取有多少人参与了评价,分析一下页面: 如果只爬取这个 <span> 标签下的数字的话,没有任何提示信息,别人看了不知道是啥东西,所以把 人评价 这三个字也爬下来的话就比较好了,但是可以看到数字和文字不在同一个元素标签下,而且文字部分还有空格,要爬取的话就要把 class="rating_people" 的 a 标签下所有的 text 提取出来,然后再去掉空格: 123456789# 参评人数# 匹配a节点value = parse_movie.xpath(\"//a[@class='rating_people']\")# 提取a节点下所有文本string = [value[0].xpath('string(.)')]# 去除多余空格number = [a.strip() for a in string]# 此时 number = ['1617307人评价'] 这样做太麻烦了,我们可以直接提取数字,得到一个列表,然后使用另一个带有提示信息的列表,将两个列表的元素合并,组成一个新列表,这个新列表的元素就是提示信息+人数123456# 参评人数value = parse_movie.xpath(\"//span[@property='v:votes']/text()\")# 合并元素number = [\" \".join(['参评人数:'] + value)]# 此时 number = ['参评人数:1617307'] 【2x03】正则表达式解析制片国家、语言接下来尝试爬取制片国家/地区、语言等信息: 分析页面可以观察到,制片国家/地区和语言结构比较特殊,没有特别的 class 或者 id 属性,所包含的层次关系也太复杂,所以这里为了简便,直接采用正则表达式来匹配信息,就没有那么复杂了: 1234567# 制片国家/地区value = re.findall('<span class=\"pl\">制片国家/地区:</span>(.*?)<br/>', movie_pages.text)country = [\" \".join(['制片国家:'] + value)]# 语言value = re.findall('<span class=\"pl\">语言:</span>(.*?)<br/>', movie_pages.text)language = [\" \".join(['语言:'] + value)] 【3x00】返回解析数据其他剩下的信息皆可利用以上方法进行提取,所有信息提取完毕,最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表 1return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url) 【4x00】数据储存模块定义一个数据保存函数 save_results() 1234def save_results(data): with open('douban.csv', 'a', encoding=\"utf-8-sig\") as fp: writer = csv.writer(fp) writer.writerow(data) 注意:编码方式要设置为 utf-8-sig,如果设置为 utf-8,则文件会乱码,不设置编码,则可能会报一下类似错误: 1UnicodeEncodeError: 'gbk' codec can't encode character '\\ub3c4' in position 9: illegal multibyte sequence 可以看到错误出现在 \\ub3c4 上,将该 Unicode 编码转换为中文为 도,发现正是排名第 19 的电影:熔炉 도가니,因为标题有韩文,所以在储存为 CSV 文件时会报编码错误,而将编码设置为 utf-8-sig 就不会报错,具体原因参见:《Python 中文日文汉字乱码处理utf-8-sig》 接下来是保存电影的海报到本地: 1234567891011# 保存电影海报poster = parse_movie.xpath(\"//div[@id='mainpic']/a/img/@src\")response = requests.get(poster[0])name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0])poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg'dir_name = 'douban_poster'if not os.path.exists(dir_name): os.mkdir(dir_name)poster_path = dir_name + '/' + poster_namewith open(poster_path, \"wb\")as f: f.write(response.content) 解析电影详情页,使用 Xpath 提取海报的 URL,向该 URL 发送请求 图片以 排名+电影名.jpg 的方式命名,但是由于提取的电影名部分含有特殊字符,比如排名第 10 的电影:忠犬八公的故事 Hachi: A Dog’s Tale,其中有个冒号,而 Windows 文件命名是不能包含这些字符的,所以我们直接去除电影名包含的英文字符、空白字符、特殊字符,只留下中文,代码实现: name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0]) 定义一个文件夹名称 douban_poster,利用 os 模块判断当前是否存在该文件夹,若不存在就创建一个 最后以二进制形式保存海报到当前目录的 douban_poster 文件夹下 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-09-27# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: douban.py# @Software: PyCharm# =============================================import requestsfrom lxml import etreeimport csvimport reimport timeimport osheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def index_pages(number): url = 'https://movie.douban.com/top250?start=%s&filter=' % number index_response = requests.get(url=url, headers=headers) tree = etree.HTML(index_response.text) m_urls = tree.xpath(\"//li/div/div/a/@href\") return m_urlsdef parse_pages(url): movie_pages = requests.get(url=url, headers=headers) parse_movie = etree.HTML(movie_pages.text) # 排名 ranking = parse_movie.xpath(\"//span[@class='top250-no']/text()\") # 电影名 name = parse_movie.xpath(\"//h1/span[1]/text()\") # 评分 score = parse_movie.xpath(\"//div[@class='rating_self clearfix']/strong/text()\") # 参评人数 value = parse_movie.xpath(\"//span[@property='v:votes']/text()\") number = [\" \".join(['参评人数:'] + value)] # value = parse_movie.xpath(\"//a[@class='rating_people']\") # string = [value[0].xpath('string(.)')] # number = [a.strip() for a in string] # print(number) # 类型 value = parse_movie.xpath(\"//span[@property='v:genre']/text()\") types = [\" \".join(['类型:'] + value)] # 制片国家/地区 value = re.findall('<span class=\"pl\">制片国家/地区:</span>(.*?)<br/>', movie_pages.text) country = [\" \".join(['制片国家:'] + value)] # 语言 value = re.findall('<span class=\"pl\">语言:</span>(.*?)<br/>', movie_pages.text) language = [\" \".join(['语言:'] + value)] # 上映时期 value = parse_movie.xpath(\"//span[@property='v:initialReleaseDate']/text()\") date = [\" \".join(['上映日期:'] + value)] # 片长 value = parse_movie.xpath(\"//span[@property='v:runtime']/text()\") time = [\" \".join(['片长:'] + value)] # 又名 value = re.findall('<span class=\"pl\">又名:</span>(.*?)<br/>', movie_pages.text) other_name = [\" \".join(['又名:'] + value)] # 导演 value = parse_movie.xpath(\"//div[@id='info']/span[1]/span[@class='attrs']/a/text()\") director = [\" \".join(['导演:'] + value)] # 编剧 value = parse_movie.xpath(\"//div[@id='info']/span[2]/span[@class='attrs']/a/text()\") screenwriter = [\" \".join(['编剧:'] + value)] # 主演 value = parse_movie.xpath(\"//div[@id='info']/span[3]\") performer = [value[0].xpath('string(.)')] # URL m_url = ['豆瓣链接:' + movie_url] # IMDb链接 value = parse_movie.xpath(\"//div[@id='info']/a/@href\") imdb_url = [\" \".join(['IMDb链接:'] + value)] # 保存电影海报 poster = parse_movie.xpath(\"//div[@id='mainpic']/a/img/@src\") response = requests.get(poster[0]) name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0]) poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg' dir_name = 'douban_poster' if not os.path.exists(dir_name): os.mkdir(dir_name) poster_path = dir_name + '/' + poster_name with open(poster_path, \"wb\")as f: f.write(response.content) return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)def save_results(data): with open('douban.csv', 'a', encoding=\"utf-8-sig\") as fp: writer = csv.writer(fp) writer.writerow(data)if __name__ == '__main__': num = 0 for i in range(0, 250, 25): movie_urls = index_pages(i) for movie_url in movie_urls: results = parse_pages(movie_url) for result in results: num += 1 save_results(result) print('第' + str(num) + '条电影信息保存完毕!') time.sleep(3) 【6x00】数据截图 【7x00】程序不足的地方程序不足的地方:豆瓣电影有反爬机制,当程序爬取到大约 150 条数据的时候,IP 就会被封掉,第二天 IP 才会解封,可以考虑综合使用多个代理、多个 User-Agent、随机时间暂停等方法进行爬取","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"豆瓣电影","slug":"豆瓣电影","permalink":"https://www.itrhx.com/tags/豆瓣电影/"}]},{"title":"Python3 爬虫实战 — 猫眼电影TOP100","slug":"A51-pyspider-maoyantop100","date":"2019-09-24T11:31:56.965Z","updated":"2019-10-21T04:00:20.669Z","comments":true,"path":"2019/09/24/A51-pyspider-maoyantop100/","link":"","permalink":"https://www.itrhx.com/2019/09/24/A51-pyspider-maoyantop100/","excerpt":"爬取时间:2019-09-23爬取难度:★☆☆☆☆☆请求链接:https://maoyan.com/board/4爬取目标:猫眼 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址,数据保存为 CSV 文件涉及知识:请求库 requests、解析库 lxml、Xpath 语法、CSV 文件储存完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/maoyan-top100其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-09-23爬取难度:★☆☆☆☆☆请求链接:https://maoyan.com/board/4爬取目标:猫眼 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址,数据保存为 CSV 文件涉及知识:请求库 requests、解析库 lxml、Xpath 语法、CSV 文件储存完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/maoyan-top100其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察猫眼电影TOP100榜,请求地址为:https://maoyan.com/board/4 每页展示10条电影信息,翻页观察 url 变化: 第一页:https://maoyan.com/board/4 第二页:https://maoyan.com/board/4?offset=10 第三页:https://maoyan.com/board/4?offset=20 一共有10页,利用一个 for 循环,从 0 到 100 每隔 10 取一个值拼接到 url,实现循环爬取每一页 12345678def index_page(number): url = 'https://maoyan.com/board/4?offset=%s' % number response = requests.get(url=url, headers=headers) return response.textif __name__ == '__main__': for i in range(0, 100, 10): index = index_page(i) 【2x00】解析模块定义一个页面解析函数 parse_page(),使用 lxml 解析库的 Xpath 方法依次提取电影排名(ranking)、电影名称(movie_name)、主演(performer)、上映时间(releasetime)、评分(score)、电影封面图 url(movie_img) 通过对主演部分的提取发现有多余的空格符和换行符,循环 performer 列表,使用 strip() 方法去除字符串头尾空格和换行符 电影评分分为整数部分和小数部分,依次提取两部分,循环遍历组成一个完整的评分 最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表 123456789101112131415161718def parse_page(content): tree = etree.HTML(content) # 电影排名 ranking = tree.xpath(\"//dd/i/text()\") # 电影名称 movie_name = tree.xpath('//p[@class=\"name\"]/a/text()') # 主演 performer = tree.xpath(\"//p[@class='star']/text()\") performer = [p.strip() for p in performer] # 上映时间 releasetime = tree.xpath('//p[@class=\"releasetime\"]/text()') # 评分 score1 = tree.xpath('//p[@class=\"score\"]/i[@class=\"integer\"]/text()') score2 = tree.xpath('//p[@class=\"score\"]/i[@class=\"fraction\"]/text()') score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))] # 电影封面图 movie_img = tree.xpath('//img[@class=\"board-img\"]/@data-src') return zip(ranking, movie_name, performer, releasetime, score, movie_img) 【3x00】数据储存模块定义一个 save_results() 函数,将所有数据保存到 maoyan.csv 文件 1234def save_results(result): with open('maoyan.csv', 'a') as fp: writer = csv.writer(fp) writer.writerow(result) 【4x00】完整代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-09-23# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: maoyan.py# @Software: PyCharm# =============================================import requestsfrom lxml import etreeimport csvheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def index_page(number): url = 'https://maoyan.com/board/4?offset=%s' % number response = requests.get(url=url, headers=headers) return response.textdef parse_page(content): tree = etree.HTML(content) # 电影排名 ranking = tree.xpath(\"//dd/i/text()\") # 电影名称 movie_name = tree.xpath('//p[@class=\"name\"]/a/text()') # 主演 performer = tree.xpath(\"//p[@class='star']/text()\") performer = [p.strip() for p in performer] # 上映时间 releasetime = tree.xpath('//p[@class=\"releasetime\"]/text()') # 评分 score1 = tree.xpath('//p[@class=\"score\"]/i[@class=\"integer\"]/text()') score2 = tree.xpath('//p[@class=\"score\"]/i[@class=\"fraction\"]/text()') score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))] # 电影封面图 movie_img = tree.xpath('//img[@class=\"board-img\"]/@data-src') return zip(ranking, movie_name, performer, releasetime, score, movie_img)def save_results(result): with open('maoyan.csv', 'a') as fp: writer = csv.writer(fp) writer.writerow(result)if __name__ == '__main__': print('开始爬取数据...') for i in range(0, 100, 10): index = index_page(i) results = parse_page(index) for i in results: save_results(i) print('数据爬取完毕!') 【4x00】数据截图","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"猫眼电影","slug":"猫眼电影","permalink":"https://www.itrhx.com/tags/猫眼电影/"}]},{"title":"Python3 爬虫学习笔记 C18","slug":"A50-Python3-spider-C18","date":"2019-09-21T03:59:30.358Z","updated":"2019-09-24T12:41:19.337Z","comments":true,"path":"2019/09/21/A50-Python3-spider-C18/","link":"","permalink":"https://www.itrhx.com/2019/09/21/A50-Python3-spider-C18/","excerpt":"Python3 爬虫学习笔记第十八章 —— 【爬虫框架 pyspider — 深入理解】","text":"Python3 爬虫学习笔记第十八章 —— 【爬虫框架 pyspider — 深入理解】 【18.1】启动参数常用启动命令:pyspider all,完整命令结构为:pyspider [OPTIONS] COMMAND [ARGS],OPTIONS 为可选参数,包含以下参数: -c, –config FILENAME:指定配置文件名称 –logging-config TEXT:日志配置文件名称,默认: pyspider/pyspider/logging.conf –debug:开启调试模式 –queue-maxsize INTEGER:队列的最大长度 –taskdb TEXT:taskdb 的数据库连接字符串,默认: sqlite –projectdb TEXT:projectdb 的数据库连接字符串,默认: sqlite –resultdb TEXT:resultdb 的数据库连接字符串,默认: sqlite –message-queue TEXT:消息队列连接字符串,默认: multiprocessing.Queue –phantomjs-proxy TEXT:PhantomJS 使用的代理,ip:port 的形式 –data-path TEXT:数据库存放的路径 –add-sys-path / –not-add-sys-path:将当前工作目录添加到python lib搜索路径 –version:显示 pyspider 的版本信息 –help:显示帮助信息 配置文件为一个 JSON 文件,一般为 config.json 文件,常用配置如下: 123456789101112{ \"taskdb\": \"mysql+taskdb://username:password@host:port/taskdb\", \"projectdb\": \"mysql+projectdb://username:password@host:port/projectdb\", \"resultdb\": \"mysql+resultdb://username:password@host:port/resultdb\", \"message_queue\": \"amqp://username:password@host:port/%2F\", \"webui\": { \"port\": 5000, \"username\": \"some_name\", \"password\": \"some_passwd\", \"need-auth\": true }} 可以设置对应的用户名,密码,端口等信息,使用命令 pyspider -c config.json all 即可运行 【18.2】运行单个组件pyspider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分,都可以单独运行,基本命令: pyspider [component_name] [options] 【18.2.1】运行 Scheduler1pyspider scheduler [OPTIONS] 123456789101112Options: --xmlrpc /--no-xmlrpc --xmlrpc-host TEXT --xmlrpc-port INTEGER --inqueue-limit INTEGER 任务队列的最大长度,如果满了则新的任务会被忽略 --delete-time INTEGER 设置为 delete 标记之前的删除时间 --active-tasks INTEGER 当前活跃任务数量配置 --loop-limit INTEGER 单轮最多调度的任务数量 --fail-pause-num INTEGER 上次失败时自动暂停项目暂停次数,任务失败,将0设置为禁用 --scheduler-cls TEXT Scheduler 使用的类 --threads TEXT ThreadBaseScheduler 的线程号,默认值:4 --help 显示帮助信息 【18.2.2】运行 Fetcher1pyspider fetcher [OPTIONS] 123456789101112Options: --xmlrpc /--no-xmlrpc --xmlrpc-host TEXT --xmlrpc-port INTEGER --poolsize INTEGER 同时请求的个数 --proxy TEXT 使用的代理 --user-agent TEXT 使用的 User-Agent --timeout TEXT 超时时间 --phantomjs-endpoint TEXT phantomjs 的端点,通过 pyspider 启动 phantomjs --splash-endpoint TEXT 执行 splash 的端点:http://splash.readthedocs.io/en/stable/api.html execut --fetcher-cls TEXT Fetcher 使用的类 --help 显示帮助信息 【18.2.3】运行 Processer1pyspider processor [OPTIONS] 1234Options: --processor-cls TEXT Processor 使用的类 --process-time-limit INTEGER 脚本处理时间限制 --help 显示帮助信息 【18.2.4】运行 WebUI1pyspider webui [OPTIONS] 1234567891011121314Options: --host TEXT 运行地址 --port INTEGER 运行端口 --cdn TEXT JS 和 CSS 的 CDN 服务器 --scheduler-rpc TEXT Scheduler 的 xmlrpc 路径 --fetcher-rpc TEXT Fetcher 的 xmlrpc 路径 --max-rate FLOAT 每个项目最大的 rate 值 --max-burst FLOAT 每个项目最大的 burst 值 --username TEXT Auth 验证的用户名 --password TEXT Auth 验证的密码 --need-auth 是否需要验证 --webui-instance TEXT 运行时使用的 Flask 应用 --process-time-limit INTEGER 调试中的脚本处理时间限制 --help 显示帮助信息 【18.3】crawl() 方法各参数参数文档:http://docs.pyspider.org/en/latest/apis/self.crawl/ url:爬取目标 URL,可以定义为单个 URL 字符串,也可以定义成 URL 列表 callback:回调函数,指定了该 URL 对应的响应内容用哪个方法来解析,示例: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.index_page) 代码解释:指定 callback 为 index_page,代表爬取 http://www.itrhx.com/ 得到的响应会用 index_page() 方法来解析,而 index_page() 方法的第一个参数就是响应对象,如下所示: 12def index_page(self, response): pass age:任务的有效时间,如果某个任务在有效时间内且已经被执行,则它不会重复执行,有如下两种设置方法: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, age=10*24*60*60) 123@config(age=10 * 24 * 60 * 60)def callback(self): pass priority:爬取任务的优先级,其值默认是 0,priority 的数值越大,对应的请求会越优先被调度,如下所示,2.html 页面将会优先爬取: 123def index_page(self): self.crawl('http://www.itrhx.com/1.html', callback=self.index_page) self.crawl('http://www.itrhx.com/2.html', callback=self.detail_page, priority=1) exetime:设置定时任务,其值是时间戳,默认是 0,即代表立即执行,如下所示表示该任务会在 30 分钟之后执行: 123import timedef on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, exetime=time.time()+30*60) retries:定义重试次数,其值默认是 3 itag:设置判定网页是否发生变化的节点值,在爬取时会判定次当前节点是否和上次爬取到的节点相同。如果节点相同,则证明页面没有更新,就不会重复爬取,如下所示: 123def index_page(self, response): for item in response.doc('.item').items(): self.crawl(item.find('a').attr.url, callback=self.detail_page, itag=item.find('.update-time').text()) 代码解释:设置 update-time 这个节点的值为 itag,在下次爬取时就会首先检测这个值有没有发生变化,如果没有变化,则不再重复爬取,否则执行爬取 auto_recrawl:开启时,爬取任务在过期后会重新执行,循环时间即定义的 age 时间长度,如下所示: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, age=5*60*60, auto_recrawl=True) 代码解释:定义 age 有效期为 5 小时,设置了 auto_recrawl 为 True,这样任务就会每 5 小时执行一次 method:HTTP 请求方式,默认为 GET,如果想发起 POST 请求,可以将 method 设置为 POST params:定义 GET 请求参数,如下所示表示两个等价的爬取任务: 123def on_start(self): self.crawl('http://httpbin.org/get', callback=self.callback, params={'a': 123, 'b': 'c'}) self.crawl('http://httpbin.org/get?a=123&b=c', callback=self.callback) data:POST 表单数据,当请求方式为 POST 时,我们可以通过此参数传递表单数据,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', data={'a': 123, 'b': 'c'}) files:上传的文件,需要指定文件名,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', files={field: {filename: 'content'}}) user_agent:爬取使用的 User-Agent headers:爬取时使用的 Headers,即 Request Headers cookies:爬取时使用的 Cookies,为字典格式 connect_timeout:在初始化连接时的最长等待时间,默认为 20 秒 timeout:抓取网页时的最长等待时间,默认为 120 秒 allow_redirects:确定是否自动处理重定向,默认为 True validate_cert:确定是否验证证书,此选项对 HTTPS 请求有效,默认为 True proxy:爬取时使用的代理,支持用户名密码的配置,格式为 username:password@hostname:port,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/get', callback=self.callback, proxy='127.0.0.1:9743') 也可以设置 craw_config 来实现全局配置,如下所示: 12class Handler(BaseHandler): crawl_config = {'proxy': '127.0.0.1:9743'} fetch_type:开启 PhantomJS 渲染,如果遇到 JavaScript 渲染的页面,指定此字段即可实现 PhantomJS 的对接,pyspider 将会使用 PhantomJS 进行网页的抓取,如下所示: 12def on_start(self): self.crawl('https://www.taobao.com', callback=self.index_page, fetch_type='js') js_script:页面加载完毕后执行的 JavaScript 脚本,如下所示,页面加载成功后将执行页面混动的 JavaScript 代码,页面会下拉到最底部: 1234567def on_start(self): self.crawl('http://www.example.org/', callback=self.callback, fetch_type='js', js_script=''' function() {window.scrollTo(0,document.body.scrollHeight); return 123; } ''') js_run_at:代表 JavaScript 脚本运行的位置,是在页面节点开头还是结尾,默认是结尾,即 document-end js_viewport_width/js_viewport_height:JavaScript 渲染页面时的窗口大小 load_images:在加载 JavaScript 页面时确定是否加载图片,默认为否 save:在不同的方法之间传递参数,如下所示: 123456def on_start(self): self.crawl('http://www.example.org/', callback=self.callback, save={'page': 1})def callback(self, response): return response.save['page'] cancel:取消任务,如果一个任务是 ACTIVE 状态的,则需要将 force_update 设置为 True force_update:即使任务处于 ACTIVE 状态,那也会强制更新状态 【18.4】任务区分pyspider 判断两个任务是否是重复的是使用的是该任务对应的 URL 的 MD5 值作为任务的唯一 ID,如果 ID 相同,那么两个任务就会判定为相同,其中一个就不会爬取了 某些情况下,请求的链接是同一个,但是 POST 的参数不同,这时可以重写 task_id() 方法,利用 URL 和 POST 的参数来生成 ID,改变这个 ID 的计算方式来实现不同任务的区分: 1234import jsonfrom pyspider.libs.utils import md5stringdef get_taskid(self, task): return md5string(task['url']+json.dumps(task['fetch'].get('data', ''))) 【18.5】全局配置pyspider 可以使用 crawl_config 来指定全局的配置,配置中的参数会和 crawl() 方法创建任务时的参数合并: 12345class Handler(BaseHandler): crawl_config = { 'headers': {'User-Agent': 'GoogleBot',} 'proxy': '127.0.0.1:9743' } 【18.6】定时爬取通过 every 属性来设置爬取的时间间隔,如下代码表示每天执行一次爬取: 1234@every(minutes=24 * 60)def on_start(self): for url in urllist: self.crawl(url, callback=self.index_page) 注意事项:如果设置了任务的有效时间(age 参数),因为在有效时间内爬取不会重复,所以要把有效时间设置得比重复时间更短,这样才可以实现定时爬取 错误举例:设定任务的过期时间为 5 天,而自动爬取的时间间隔为 1 天,当第二次尝试重新爬取的时候,pyspider 会监测到此任务尚未过期,便不会执行爬取: 1234567@every(minutes=24 * 60)def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.index_page)@config(age=5 * 24 * 60 * 60)def index_page(self): pass","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"pyspider","slug":"pyspider","permalink":"https://www.itrhx.com/tags/pyspider/"}]},{"title":"Python3 爬虫学习笔记 C17","slug":"A49-Python3-spider-C17","date":"2019-09-18T06:18:23.904Z","updated":"2019-09-24T12:41:15.652Z","comments":true,"path":"2019/09/18/A49-Python3-spider-C17/","link":"","permalink":"https://www.itrhx.com/2019/09/18/A49-Python3-spider-C17/","excerpt":"Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】","text":"Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】 【17.1】初识 pyspiderpyspider 是由国人 Binux 编写的一个 Python 爬虫框架 GitHub:https://github.com/binux/pyspider 官方文档(英文):http://docs.pyspider.org/ 非官方文档(中文):http://book.crifan.com/books/python_spider_pyspider/website/ 非官方文档(中文):https://www.cntofu.com/book/156/index.md pyspider 特性: python 脚本控制,可以使用任何 html 解析包(内置 pyquery) WEB 界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出 支持 MySQL、MongoDB、Redis、SQLite、Elasticsearch、PostgreSQL 对接了 PhantomJS,支持抓取 JavaScript 的页面 组件可替换,支持单机和分布式部署,支持 Docker 部署 提供优先级控制、失败重试、定时抓取等功能 Windows 系统安装 pyspider: 使用命令 pip install pyspider 安装,若报 PyCurl 相关错误,可访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl 下载对应 wheel 文件并使用命令 pip install whl文件名 安装即可 如果要爬取 JavaScrip 渲染的页面,还要下载 PhantomJS,并将 PhantomJS 的路径配置到环境变量里,或者直接复制到 Python 安装目录的 Scripts 文件夹,需要用到数据库储存的话,同样要安装好相应的数据库 准备就绪后,使用 pyspider all 命令可启动 pyspider,浏览器打开:http://localhost:5000/ 可以看到 pyspider 的 WebUI 管理界面 【17.2】使用 pyspider 【17.2.1】主界面当成功创建了一个爬虫项目后,主界面如下所示: Recent Active Tasks:查看最近活动的任务,会跳转到一个页面有列表显示 Create:创建一个新的爬虫项目 group:定义项目的分组,以方便管理,若 group 设置为 delete,则该项目将会在24小时之后删除 project name:爬虫项目名称 status:项目状态,各状态如下: TODO:一个爬虫项目刚刚创建时的状态,此状态下可以编辑 Python 代码 STOP:中止项目的运行 CHECKING:当一个运行中的项目被编辑时项目状态会被自动设置成此状态并中止运行 DEBUG:会运行爬虫,顾名思义找 BUG,一般来说用于调试阶段 RUNNING:运行爬虫项目 PAUSED:项目暂停运行,默认没有这个状态,但是当你在运行过程中突然断网就会出现此状态 rate/burst:当前的爬取速率,rate 代表 1 秒发出多少个请求,burst 相当于流量控制中的令牌桶算法的令牌数,rate 和 burst 设置的越大,爬取速率越快,速率的设定需要考虑本机性能和爬取过快被封的问题 avg time:任务平均时间 process:5m、1h、1d 分别指的是最近 5 分、1 小时、1 天内的请求情况,all 代表所有的请求情况,请求由不同颜色表示,蓝色的代表等待被执行的请求,绿色的代表成功的请求,黄色的代表请求失败后等待重试的请求,红色的代表失败次数过多而被忽略的请求 actions:对爬虫项目的操作,各操作如下: Run:立即执行任务,需要 status 为 RUNNING 或者 DEBUG 状态;假如在配置的调度执行时间内已经执行过,再点 run 是无效的,需要删除 task.db 里的数据才行 Active Tasks:查看当前爬虫项目的活动任务 Results:查看项目运行结果 【17.2.2】项目界面创建一个爬虫项目,界面如下所示: 创建项目:点击 Create 即可新建一个爬虫项目 Project Name:爬虫项目名称 Start URL(s) :爬虫入口地址,选填,可在项目中更改 项目创建完成进入调试界面: 调试界面右边:编写代码的区域 调试界面左边:调试的区域,用于执行代码,显示输出信息等用途 run:单步调试爬虫程序,点击就可运行当前任务 < > 箭头:上一步、下一步,用于调试过程中切换到上一步骤或者下一步骤 save:保存当前代码,当代码变更后只有保存了再运行才能得到最新结果 enable css selector helper: CSS 选择器辅助程序 web:页面预览 html:可以查看页面源代码 follows:表示爬取请求,点击可查看所有的请求 在新建一个爬虫项目的时候,pyspider 已经自动生成了如下代码: 123456789101112131415161718192021222324252627#!/usr/bin/env python# -*- encoding: utf-8 -*-# Created on 2019-09-17 21:18:13# Project: 2from pyspider.libs.base_handler import *class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('__START_URL__', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) @config(priority=2) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } class Handler():pyspider 爬虫的主类,可以在此处定义爬取、解析、存储的逻辑。整个爬虫的功能只需要一个 Handler 即可完成 crawl_config 属性:项目的所有爬取配置将会统一定义到这里,如定义 headers、设置代理等,配置之后全局生效 on_start() 方法:爬取入口,初始的爬取请求会在这里产生,该方法通过调用 crawl() 方法即可新建一个爬取请求,第一个参数是爬取的 URL,另一个参数 callback 指定了这个页面爬取成功后用哪个方法进行解析,默认指定为 index_page() 方法,即如果这个 URL 对应的页面爬取成功了,那 Response 将交给 index_page() 方法解析 index_page() 方法:接收 Response 参数,Response 对接了 pyquery。直接调用 doc() 方法传入相应的 CSS 选择器,就可以像 pyquery 一样解析此页面,代码中默认是 a[href^="http"],即解析页面的所有链接,然后将链接遍历,再次调用了 crawl() 方法生成了新的爬取请求,同时再指定了 callback 为 detail_page,表示这些页面爬取成功了就调用 detail_page() 方法解析。index_page() 实现了两个功能,一是将爬取的结果进行解析,二是生成新的爬取请求 detail_page() 方法:同样接收 Response 作为参数。detail_page() 抓取的就是详情页的信息,就不会生成新的请求,只对 Response 对象做解析,解析之后将结果以字典的形式返回。当然也可以进行后续处理,如将结果保存到数据库等操作 PS:pyspider 默认的 web 预览页面窗口较小,可以找到 pyspider 文件夹有个 debug.min.css 文件(如:E:\\Python\\Lib\\site-packages\\pyspider\\webui\\static\\debug.min.css),搜索 iframe,将原样式:iframe{border-width:0;width:100%} 改为 iframe{border-width:0;width:100%;height:400px !important} 即可,清除浏览器缓存后就会生效! 【17.3】使用 pyspider 爬取去哪儿网爬取地址:http://travel.qunar.com/travelbook/list.htm爬取目标:去哪儿网旅游攻略,发帖作者、标题、正文等 【17.3.1】爬取首页创建一个名为 qunar 的爬虫项目,Start URL 设置为 http://travel.qunar.com/travelbook/list.htm ,点击 run 出现一个爬取请求 左边调试区域出现以下代码: 12345678{ \"process\": { \"callback\": \"on_start\" }, \"project\": \"qunar\", \"taskid\": \"data:,on_start\", \"url\": \"data:,on_start\"} callback 为 on_start,表示此时执行了 on_start() 方法。在 on_start() 方法中,利用 crawl() 方法即可生成一个爬取请求,点击 index_page 链接后面的箭头会出现许多新的爬取请求,即首页所包含的所有链接 此时左边调试区域代码变为: 123456789101112{ \"fetch\": {}, \"process\": { \"callback\": \"index_page\" }, \"project\": \"qunar\", \"schedule\": { \"age\": 864000 }, \"taskid\": \"73a789f99528a2bdc3ab83a13902962a\", \"url\": \"http://travel.qunar.com/travelbook/list.htm\"} callback 变为了 index_page,表示此时执行了 index_page() 方法。传入 index_page() 方法的 response 参数为刚才生成的第一个爬取请求的 response 对象,然后调用 doc() 方法,传入提取所有 a 节点的 CSS 选择器,获取 a 节点的属性 href,实现了页面所有链接的提取,随后遍历所有链接,调用 crawl() 方法,把每个链接构造成新的爬取请求,可以看到 follows 新生成了 229 个爬取请求。点击 web 按钮可以直接预览当前页面,点击 html 按钮可以查看此页面源代码 【17.3.2】信息匹配代码 for each in response.doc('a[href^="http"]').items(): 实现了对整个页面链接的获取,我们需要提取网页的攻略的标题,内容等信息,那么直接替换 doc() 方法里的匹配语句即可,pyspider 提供了非常方便的 CSS 选择器,点击 enable css selector helper 按钮后,选择要匹配的信息并点击,再点击箭头 add to editor 即可得到匹配语句 完成了 CSS 选择器的替换,点击 save 保存,再次点击 run 重新执行 index_page() 方法,可以看到 follows 变为了 10 个,即抓取到了 10 篇攻略 【17.3.3】抓取下一页数据每一页只有 10 篇攻略,想要爬取所有页面的攻略,必须要得到下一页的数据,优化 index_page() 方法: 123456@config(age=10 * 24 * 60 * 60)def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page) next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) 匹配下一页按钮,获取下一页按钮的 URL 并赋值给 next,将该 URL 传给 crawl() 方法,指定回调函数为 index_page() 方法,这样会再次调用 index_page() 方法,提取下一页的攻略标题 【17.3.4】抓取JS渲染数据随便点击一个获取到的攻略,预览该页面,可以观察到头图一直在加载中,切换到 html 查看源代码页面,可以观察到没有 img 节点,那么此处就是后期经过 JavaScript 渲染后才出现的 针对 JavaScript 渲染页面,可以通过 PhantomJS 来实现,具体到 pyspider 中,只需要在 index_page() 的 crawl() 抓取方法中添加一个参数 fetch_type 即可: 123456@config(age=10 * 24 * 60 * 60)def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js') next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) 保存之后再次运行即可看到正常页面 【17.3.5】抓取所有数据改写 detail_page() 方法,同样通过 CSS 选择器提取 URL、标题、日期、作者、正文、图片等信息: 1234567891011@config(priority=2)def detail_page(self, response): return { 'url': response.url, 'title': response.doc('#booktitle').text(), 'date': response.doc('.when .data').text(), 'day': response.doc('.howlong .data').text(), 'who': response.doc('.who .data').text(), 'text': response.doc('#b_panel_schedule').text(), 'image': response.doc('.cover_img').attr.src } 【17.3.6】启动爬虫项目该爬虫项目完整代码如下: 12345678910111213141516171819202122232425262728293031323334#!/usr/bin/env python# -*- encoding: utf-8 -*-# Created on 2019-09-18 09:48:29# Project: qunarfrom pyspider.libs.base_handler import *class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js') next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) @config(priority=2) def detail_page(self, response): return { 'url': response.url, 'title': response.doc('#booktitle').text(), 'date': response.doc('.when .data').text(), 'day': response.doc('.howlong .data').text(), 'who': response.doc('.who .data').text(), 'text': response.doc('#b_panel_schedule').text(), 'image': response.doc('.cover_img').attr.src } 保存代码后,回到主界面,将项目 status 修改为 RUNNING ,点击 actions 的 run 按钮即可启动爬虫 点击 Active Tasks,即可查看最近请求的详细状况: 点击 Results,即可查看所有的爬取结果: 另外,右上角还可以选择 JSON、CSV 格式","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"pyspider","slug":"pyspider","permalink":"https://www.itrhx.com/tags/pyspider/"}]},{"title":"Hexo 博客提交百度、谷歌搜索引擎收录","slug":"A48-submit-search-engine-inclusion","date":"2019-09-17T07:59:46.143Z","updated":"2019-12-29T07:20:01.329Z","comments":true,"path":"2019/09/17/A48-submit-search-engine-inclusion/","link":"","permalink":"https://www.itrhx.com/2019/09/17/A48-submit-search-engine-inclusion/","excerpt":"","text":"● 写在前面(必看)网站在没有提交搜索引擎收录之前,直接搜索你网站的内容是搜不到的,只有提交搜索引擎之后,搜索引擎才能收录你的站点,通过爬虫抓取你网站的东西,对于 hexo 博客来说,如果你是部署在 GitHub Pages,那么你是无法被百度收录的,因为 GitHub 禁止了百度爬虫,最常见的解决办法是双线部署到 Coding Pages 和 GitHub Pages,因为百度爬虫可以爬取到 Coding 上的内容,从而实现百度收录,如果你的 hexo 博客还没有实现双线部署,请参考:《Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS》,另外百度收录的所需的时间较长,大约半个月左右才会看到效果! ● 查看网站是否被收录首先我们可以输入 site:域名 来查看域名是否被搜索引擎收录,如下图所示,表示没有收录: ● 百度资源平台添加网站访问百度搜索资源平台官网,注册或者登陆百度账号,依次选择【用户中心】-【站点管理】,添加你的网站,在添加站点时会让你选择协议头(http 或者 https),如果选择 https,它会验证你的站点,大约能在一天之内完成,我的网站已经实现了全站 https,因此选择了 https 协议,但是不知道为什么始终验证失败,实在是无解,只能选择 http 协议了,如果你的站点也实现了全站 https,也可以尝试一下 之后会让你验证网站所有权,提供三种验证方式: 文件验证:下载给定的文件,将其放到本地主题目录 source 文件夹,然后部署上去完成验证 HTML 标签验证:一般是给一个 meta 标签,放到首页 <head> 与 </head> 标签之间即可完成验证 CNAME 验证:个人觉得这种方法最简单,去域名 DNS 添加一个 CNAME 记录即可完成验证 ● 提交百度搜索百度提供了自动提交和手动提交两种方式,其中自动提交又分为主动推送、自动推送和 sitemap 三种方式,以下是官方给出的解释: 主动推送:最为快速的提交方式,推荐您将站点当天新产出链接立即通过此方式推送给百度,以保证新链接可以及时被百度收录 自动推送:是轻量级链接提交组件,将自动推送的 JS 代码放置在站点每一个页面源代码中,当页面被访问时,页面链接会自动推送给百度,有利于新页面更快被百度发现 sitemap:您可以定期将网站链接放到sitemap中,然后将sitemap提交给百度。百度会周期性的抓取检查您提交的sitemap,对其中的链接进行处理,但收录速度慢于主动推送 手动提交:如果您不想通过程序提交,那么可以采用此种方式,手动将链接提交给百度 四种提交方式对比: 方式 主动推送 自动推送 Sitemap 手动提交 速度 最快 —— —— —— 开发成本 高 低 中 不需开发 可提交量 低 高 高 低 是否建议提交历史连接 否 是 是 是 和其他提交方法是否有冲突 无 无 无 无 个人推荐同时使用主动推送和 sitemap 方式,下面将逐一介绍这四种提交方式的具体实现方法 ● 主动推送在博客根目录安装插件 npm install hexo-baidu-url-submit --save,然后在根目录 _config.yml 文件里写入以下配置: 12345baidu_url_submit: count: 1 # 提交最新的多少个链接 host: www.itrhx.com # 在百度站长平台中添加的域名 token: your_token # 秘钥 path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里 其中的 token 可以在【链接提交】-【自动提交】-【主动推送】下面看到,接口调用地址最后面 token=xxxxx 即为你的 token 同样是在根目录的 _config.yml 文件,大约第 17 行处,url 要改为在百度站长平台添加的域名,也就是你网站的首页地址: 1234# URLurl: https://www.itrhx.comroot: /permalink: :year/:month/:day/:title/ 最后,加入新的 deployer: 123456789# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy:- type: git repository: github: git@github.com:TRHX/TRHX.github.io.git # 这是原来的 github 配置 coding: git@git.dev.tencent.com:TRHX/TRHX.git # 这是原来的 coding 配置 branch: master- type: baidu_url_submitter # 这是新加的主动推送 最后执行 hexo g -d 部署一遍即可实现主动推送,推送成功的标志是:在执行部署命令最后会显示类似如下代码: 12{\"remain\":4999953,\"success\":47}INFO Deploy done: baidu_url_submitter 这表示有 47 个页面已经主动推送成功,remain 的意思是当天剩余的可推送 url 条数 主动推送相关原理介绍: 新链接的产生:hexo generate 会产生一个文本文件,里面包含最新的链接 新链接的提交:hexo deploy 会从上述文件中读取链接,提交至百度搜索引擎 该插件的 GitHub 地址:https://github.com/huiwang/hexo-baidu-url-submit ● 自动推送关于自动推送百度官网给出的解释是:自动推送是百度搜索资源平台为提高站点新增网页发现速度推出的工具,安装自动推送JS代码的网页,在页面被访问时,页面URL将立即被推送给百度 此时要注意,有些 hexo 主题集成了这项功能,比如 next 主题,在 themes\\next\\layout_scripts\\ 下有个 baidu_push.swig 文件,我们只需要把如下代码粘贴到该文件,然后在主题配置文件设置 baidu_push: true 即可 12345678910111213141516{% if theme.baidu_push %}<script>(function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName(\"script\")[0]; s.parentNode.insertBefore(bp, s);})();</script>{% endif %} 然而大部分主题是没有集成这项功能的,对于大部分主题来说,我们可以把以下代码粘贴到 head.ejs 文件的 <head> 与 </head> 标签之间即可,从而实现自动推送(比如我使用的是 Material X 主题,那么只需要把代码粘贴到 \\themes\\material-x\\layout\\_partial\\head.ejs 中即可) 1234567891011121314<script>(function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName(\"script\")[0]; s.parentNode.insertBefore(bp, s);})();</script> ● sitemap首先我们要使用以下命令生成一个网站地图: 12npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save 这里也注意一下,将根目录的 _config.yml 文件,大约第 17 行处,url 改为在百度站长平台添加的域名,也就是你网站的首页地址: 1234# URLurl: https://www.itrhx.comroot: /permalink: :year/:month/:day/:title/ 然后使用命令 hexo g -d 将网站部署上去,然后访问 你的首页/sitemap.xml 或者 你的首页/baidusitemap.xml 就可以看到网站地图了 比如我的是:https://www.itrhx.com/baidusitemap.xml 或者 https://www.itrhx.com/sitemap.xml 其中 sitemap.xml 文件是搜索引擎通用的 sitemap 文件,baidusitemap.xml 是百度专用的 sitemap 文件 然后来到百度站长平台的 sitemap 提交页面,将你的 sitemap 地址提交即可,如果成功的话状态会显示为正常,初次提交要等几分钟,sitemap.xml 相比 baidusitemap.xml 来说等待时间也会更长,如果以后你博客有新的文章或其他页面,可以点击手动更新文件,更新一下新的 sitemap ● 手动提交手动提交不需要其他额外操作,直接把需要收录的页面的 url 提交即可,这种方法效率较低,更新较慢,不推荐使用 ● 提交谷歌搜索提交谷歌搜索引擎比较简单,在提交之前,我们依然可以使用 site:域名 查看网站是否被收录,我的网站搭建了有差不多一年了,之前也没提交过收录,不过谷歌爬虫的确是强大,即使没有提交过,现在也能看到有一百多条结果了: 接下来我们将网站提交谷歌搜索引擎搜索,进入谷歌站长平台,登录你的谷歌账号之后会让你验证网站所有权: 有两种验证方式,分别是网域和网址前缀,两种资源类型区别如下: 网址前缀资源 网域资源 说明 仅包含具有指定前缀(包括协议 http/https)的网址。如果希望资源匹配任何协议或子网域(http/https/www./m. 等),建议改为添加网域资源。 包括所有子网域(m、www 等)和多种协议(http、https、ftp)的网域级资源。 验证 多种类型 仅 DNS 记录验证 示例 资源 http://example.com/✔ http://example.com/dresses/1234X https://example.com/dresses/1234X http://www.example.com/dresses/1234 资源 example.com✔ http://example.com/dresses/1234✔ https://example.com/dresses/1234✔ http://www.example.com/dresses/1234✔ http://support.m.example.com/dresses/1234 由对比可知选择网域资源验证方式比较好,只需要一个域名就可以匹配到多种格式的 URL,之后会给你一个 TXT 的记录值,复制它到你域名 DNS 增加一个 TXT 记录,点击验证即可 提交谷歌收录比较简单,选择站点地图,将我们之前生成的 sitemap 提交就行了,过几分钟刷新一下看到成功字样表示提交成功!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"SEO","slug":"SEO","permalink":"https://www.itrhx.com/tags/SEO/"}]},{"title":"Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS","slug":"A47-hexo-deployed-to-github-and-coding","date":"2019-09-16T06:11:40.959Z","updated":"2019-12-29T07:20:11.697Z","comments":true,"path":"2019/09/16/A47-hexo-deployed-to-github-and-coding/","link":"","permalink":"https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/","excerpt":"","text":"部署到 Coding Pages 的好处:国内访问速度更快,可以提交百度收录(GitHub 禁止了百度的爬取) 部署到 Coding Pages 的坏处:就今年来说,Coding 不太稳定,随时有宕机的可能,群里的朋友已经经历过几次了,不过相信以后会越来越稳定的 部署过程中常见的问题:无法实现全站 HTTPS,Coding 申请 SSL 证书失败,浏览器可能会提示不是安全链接 本文前提:你已经将 Hexo 成功部署到了 GitHub Pages,如果还没有,请参考:《使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】》 本文将全面讲述如何成功双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS,同时解决一些常见的问题! 1.创建项目进入 Coding 官网,点击个人版登陆,没有账号就注册一个并登录,由于 Coding 已经被腾讯收购了,所以登录就会来到腾讯云开发者平台,点击创建项目 项目名称建议和你的用户名一致,这样做的好处是:到时候可以直接通过 user_name.coding.me 访问你的博客,如果项目名与用户名不一致,则需要通过 user_name.coding.me/project_name 才能访问,项目描述可以随便写 2.配置公钥配置 SSH 公钥方法与 GitHub Pages 的方式差不多,点击你的头像,依次选择【个人设置】-【SSH公钥】-【新增公钥】 前面部署到 GitHub Pages 的时候就已经有了一对公钥,我们直接将该公钥粘贴进去就行,公钥名称可以随便写,选中永久有效选项 PS:公钥储存位置一般在 C:\\Users\\用户名\\.ssh 目录下的 id_rsa.pub 文件里,用记事本打开复制其内容即可 添加公钥后,我们可以右键 Get Bash,输入以下命令来检查是否配置成功: 1ssh -T git@git.coding.net 若出现以下提示,则证明配置成功: 12Coding 提示: Hello XXX, You've connected to Coding.net via SSH. This is a personal key.XXX,你好,你已经通过 SSH 协议认证 Coding.net 服务,这是一个个人公钥 3.配置 _config.yml进入你的项目,在右下角有选择连接方式,选择 SSH 方式(HTTPS 方式也可以,但是这种方式有时候可能连接不上,SSH 连接不容易出问题),一键复制,然后打开你本地博客根目录的 _config.yml 文件,找到 deploy 关键字,添加 coding 地址:coding: git@git.dev.tencent.com:user_name/user_name.git,也就是刚刚复制的 SSH 地址 添加完成后先执行命令 hexo clean 清理一下缓存,然后执行命令 hexo g -d 将博客双线部署到 Coding Pages 和 GitHub Pages,如下图所示表示部署成功: 4.开启 Coding Pages进入你的项目,在代码栏下选择 Pages 服务,一键开启 Coding Pages,等待几秒后刷新网页即可看到已经开启的 Coding Pages,到目前为止,你就可以通过 xxxx.coding.me(比如我的是 trhx.coding.me)访问你的 Coding Pages 页面了 5.绑定域名并开启 HPPTS首先在你的域名 DNS 设置中添加一条 CNAME 记录指向 xxxx.coding.me,解析路线选择 默认,将 GitHub 的解析路线改为 境外,这样境外访问就会走 GitHub,境内就会走 Coding,也有人说阿里云是智能解析,自动分配路线,如果解析路线都是默认,境外访问同样会智能选择走 GitHub,境内走 Coding,我没有验证过,有兴趣的可以自己试试,我的解析如下图所示: 然后点击静态 Pages 应用右上角的设置,进入设置页面,这里要注意,如果你之前已经部署到了 GitHub Pages 并开启了 HTTPS,那么直接在设置页面绑定你自己的域名,SSL/TLS 安全证书就会显示申请错误,如下图所示,没有申请到 SSL 证书,当你访问你的网站时,浏览器就会提示不是安全连接 申请错误原因是:在验证域名所有权时会定位到 Github Pages 的主机上导致 SSL 证书申请失败 正确的做法是:先去域名 DNS 把 GitHub 的解析暂停掉,然后再重新申请 SSL 证书,大约十秒左右就能申请成功,然后开启强制 HTTPS 访问 这里也建议同时绑定有 www 前缀和没有 www 前缀的,如果要绑定没有 www 前缀的,首先要去域名 DNS 添加一个 A 记录,主机记录为 @,记录值为你博客 IP 地址,IP 地址可以在 cmd 命令行 ping 一下得到,然后在 Coding Pages 中设置其中一个为【首选】,另一个设置【跳转至首选】,这样不管用户是否输入 www 前缀都会跳到有 www 前缀的了 在博客资源引用的时候也要注意所有资源的 URL 必须是以 https:// 开头,不然浏览器依旧会提示不安全! 至此,我们的 Hexo 博客就成功双线部署到 Coding Pages 和 GitHub Pages 了,并且也实现了全站 HPPTS,最后来一张 GitHub Pages 和 Coding Pages 在国内的速度对比图,可以明显看到速度的提升","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"Coding Pages","slug":"Coding-Pages","permalink":"https://www.itrhx.com/tags/Coding-Pages/"},{"name":"GitHub Pages","slug":"GitHub-Pages","permalink":"https://www.itrhx.com/tags/GitHub-Pages/"}]},{"title":"Python3 爬虫学习笔记 C16","slug":"A46-Python3-spider-C16","date":"2019-09-13T16:44:50.577Z","updated":"2019-09-24T12:43:19.863Z","comments":true,"path":"2019/09/14/A46-Python3-spider-C16/","link":"","permalink":"https://www.itrhx.com/2019/09/14/A46-Python3-spider-C16/","excerpt":"Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】","text":"Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】 【16.1】关于 RedisRedis 是一个基于内存的高效的键值型(key-value)非关系型数据库,它支持存储的 value 类型非常多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合) 和 hash(哈希类型),它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,此外,它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。 【16.2】使用 Redis首先安装 Redis 和 redis-py 库,管理 Redis 可以使用可视化工具 Redis Desktop Manager,该工具现在收费了,分享个 0.8.8.384 的免费版本 安装 redis-py 库:pip install redisRedis 官网:https://redis.io官方文档:https://redis.io/documentation中文官网:http://www.redis.cn中文教程:http://www.runoob.com/redis/redis-tutorial.htmlGitHub:https://github.com/antirez/redisRedis Windows下载地址一:https://github.com/microsoftarchive/redis/releases (最新版 3.2.100,似乎不再更新)Redis Windows下载地址二:https://github.com/tporadowski/redis/releases (最新版)Redis Desktop Manager 官网:https://redisdesktop.com/Redis Desktop Manager 0.8.8.384 免费版:https://pan.baidu.com/s/18MKeCqT0MG0hc89jfkpIkA (提取码:3ovc) 利用 Python 连接 Redis 示例: 12345from redis import StrictRedisredis = StrictRedis(host='localhost', port=6379, db=0, password='000000')redis.set('name', 'TRHX')print(redis.get('name')) 传入 Redis 的地址、运行端口、使用的数据库和密码, 4 个参数默认值分别为 localhost、6379、0 和 None,声明一个 StrictRedis 对象,调用 set() 方法,设置一个键值对,输出结果如下: 1b'TRHX' 另外也可以使用 ConnectionPool 来连接: 1234from redis import StrictRedis, ConnectionPool pool = ConnectionPool(host='localhost', port=6379, db=0, password='000000') redis = StrictRedis(connection_pool=pool) ConnectionPool 也支持通过 URL 来构建: 123redis://[:password]@host:port/db # 创建 Redis TCP 连接rediss://[:password]@host:port/db # 创建 Redis TCP+SSL 连接unix://[:password]@/path/to/socket.sock?db=db # 创建 Redis UNIX socket 连接 代码示例: 12345from redis import StrictRedis, ConnectionPoolurl = 'redis://:000000@localhost:6379/0' pool = ConnectionPool.from_url(url) redis = StrictRedis(connection_pool=pool) 以下是有关的键操作、字符串操作、列表操作、集合操作、散列操作的各种方法,记录一下,方便查阅来源:《Python3 网络爬虫开发实战(崔庆才著)》Redis 命令参考:http://redisdoc.com/ 、http://doc.redisfans.com/ 【16.3】Key(键)操作 方法 作用 参数说明 示例 示例说明 示例结果 exists(name) 判断一个键是否存在 name:键名 redis.exists(‘name’) 是否存在 name 这个键 True delete(name) 删除一个键 name:键名 redis.delete(‘name’) 删除 name 这个键 1 type(name) 判断键类型 name:键名 redis.type(‘name’) 判断 name 这个键类型 b’string’ keys(pattern) 获取所有符合规则的键 pattern:匹配规则 redis.keys(‘n*’) 获取所有以 n 开头的键 [b’name’] randomkey() 获取随机的一个键 randomkey() 获取随机的一个键 b’name’ rename(src, dst) 重命名键 src:原键名;dst:新键名 redis.rename(‘name’, ‘nickname’) 将 name 重命名为 nickname True dbsize() 获取当前数据库中键的数目 dbsize() 获取当前数据库中键的数目 100 expire(name, time) 设定键的过期时间,单位为秒 name:键名;time:秒数 redis.expire(‘name’, 2) 将 name 键的过期时间设置为 2 秒 True ttl(name) 获取键的过期时间,单位为秒,-1 表示永久不过期 name:键名 redis.ttl(‘name’) 获取 name 这个键的过期时间 -1 move(name, db) 将键移动到其他数据库 name:键名;db:数据库代号 move(‘name’, 2) 将 name 移动到 2 号数据库 True flushdb() 删除当前选择数据库中的所有键 flushdb() 删除当前选择数据库中的所有键 True flushall() 删除所有数据库中的所有键 flushall() 删除所有数据库中的所有键 True 【16.4】String(字符串)操作 方法 作用 参数说明 示例 示例说明 示例结果 set(name, value) 给数据库中键名为 name 的 string 赋予值 value name:键名;value:值 redis.set(‘name’, ‘Bob’) 给 name 这个键的 value 赋值为 Bob True get(name) 返回数据库中键名为 name 的 string 的 value name:键名 redis.get(‘name’) 返回 name 这个键的 value b’Bob’ getset(name, value) 给数据库中键名为 name 的 string 赋予值 value 并返回上次的 value name:键名;value:新值 redis.getset(‘name’, ‘Mike’) 赋值 name 为 Mike 并得到上次的 value b’Bob’ mget(keys, *args) 返回多个键对应的 value 组成的列表 keys:键名序列 redis.mget([‘name’, ‘nickname’]) 返回 name 和 nickname 的 value [b’Mike’, b’Miker’] setnx(name, value) 如果不存在这个键值对,则更新 value,否则不变 name:键名 redis.setnx(‘newname’, ‘James’) 如果 newname 这个键不存在,则设置值为 James 第一次运行结果是 True,第二次运行结果是 False setex(name, time, value) 设置可以对应的值为 string 类型的 value,并指定此键值对应的有效期 name:键名;time:有效期;value:值 redis.setex(‘name’, 1, ‘James’) 将 name 这个键的值设为 James,有效期为 1 秒 True setrange(name, offset, value) 设置指定键的 value 值的子字符串 name:键名;offset:偏移量;value:值 redis.set(‘name’, ‘Hello’) redis.setrange (‘name’, 6, ‘World’) 设置 name 为 Hello 字符串,并在 index 为 6 的位置补 World 11,修改后的字符串长度 mset(mapping) 批量赋值 mapping:字典或关键字参数 redis.mset({‘name1’: ‘Durant’, ‘name2’: ‘James’}) 将 name1 设为 Durant,name2 设为 James True msetnx(mapping) 键均不存在时才批量赋值 mapping:字典或关键字参数 redis.msetnx({‘name3’: ‘Smith’, ‘name4’: ‘Curry’}) 在 name3 和 name4 均不存在的情况下才设置二者值 True incr(name, amount=1) 键名为 name 的 value 增值操作,默认为 1,键不存在则被创建并设为 amount name:键名;amount:增长的值 redis.incr(‘age’, 1) age 对应的值增 1,若不存在,则会创建并设置为 1 1,即修改后的值 decr(name, amount=1) 键名为 name 的 value 减值操作,默认为 1,键不存在则被创建并将 value 设置为 - amount name:键名;amount:减少的值 redis.decr(‘age’, 1) age 对应的值减 1,若不存在,则会创建并设置为-1 -1,即修改后的值 append(key, value) 键名为 key 的 string 的值附加 value key:键名 redis.append(‘nickname’, ‘OK’) 向键名为 nickname 的值后追加 OK 13,即修改后的字符串长度 substr(name, start, end=-1) 返回键名为 name 的 string 的子字符串 name:键名;start:起始索引;end:终止索引,默认为-1,表示截取到末尾 redis.substr(‘name’, 1, 4) 返回键名为 name 的值的字符串,截取索引为 1~4 的字符 b’ello’ getrange(key, start, end) 获取键的 value 值从 start 到 end 的子字符串 key:键名;start:起始索引;end:终止索引 redis.getrange(‘name’, 1, 4) 返回键名为 name 的值的字符串,截取索引为 1~4 的字符 b’ello 【16.5】Hash(哈希表)操作 方法 作用 参数说明 示例 示例说明 示例结果 hset(name, key, value) 向键名为 name 的散列表中添加映射 name:键名;key:映射键名;value:映射键值 hset(‘price’, ‘cake’, 5) 向键名为 price 的散列表中添加映射关系,cake 的值为 5 1,即添加的映射个数 hsetnx(name, key, value) 如果映射键名不存在,则向键名为 name 的散列表中添加映射 name:键名;key:映射键名;value:映射键值 hsetnx(‘price’, ‘book’, 6) 向键名为 price 的散列表中添加映射关系,book 的值为 6 1,即添加的映射个数 hget(name, key) 返回键名为 name 的散列表中 key 对应的值 name:键名;key:映射键名 redis.hget(‘price’, ‘cake’) 获取键名为 price 的散列表中键名为 cake 的值 5 hmget(name, keys, *args) 返回键名为 name 的散列表中各个键对应的值 name:键名;keys:键名序列 redis.hmget(‘price’, [‘apple’, ‘orange’]) 获取键名为 price 的散列表中 apple 和 orange 的值 [b’3’, b’7’] hmset(name, mapping) 向键名为 name 的散列表中批量添加映射 name:键名;mapping:映射字典 redis.hmset(‘price’, {‘banana’: 2, ‘pear’: 6}) 向键名为 price 的散列表中批量添加映射 True hincrby(name, key, amount=1) 将键名为 name 的散列表中映射的值增加 amount name:键名;key:映射键名;amount:增长量 redis.hincrby(‘price’, ‘apple’, 3) key 为 price 的散列表中 apple 的值增加 3 6,修改后的值 hexists(name, key) 键名为 name 的散列表中是否存在键名为键的映射 name:键名;key:映射键名 redis.hexists(‘price’, ‘banana’) 键名为 price 的散列表中 banana 的值是否存在 True hdel(name, *keys) 在键名为 name 的散列表中,删除键名为键的映射 name:键名;keys:键名序列 redis.hdel(‘price’, ‘banana’) 从键名为 price 的散列表中删除键名为 banana 的映射 True hlen(name) 从键名为 name 的散列表中获取映射个数 name:键名 redis.hlen(‘price’) 从键名为 price 的散列表中获取映射个数 6 hkeys(name) 从键名为 name 的散列表中获取所有映射键名 name:键名 redis.hkeys(‘price’) 从键名为 price 的散列表中获取所有映射键名 [b’cake’, b’book’, b’banana’, b’pear’] hvals(name) 从键名为 name 的散列表中获取所有映射键值 name:键名 redis.hvals(‘price’) 从键名为 price 的散列表中获取所有映射键值 [b’5’, b’6’, b’2’, b’6’] hgetall(name) 从键名为 name 的散列表中获取所有映射键值对 name:键名 redis.hgetall(‘price’) 从键名为 price 的散列表中获取所有映射键值对 {b’cake’: b’5’, b’book’: b’6’, b’orange’: b’7’, b’pear’: b’6’} 【16.6】List(列表)操作 方法 作用 参数说明 示例 示例说明 示例结果 rpush(name, *values) 在键名为 name 的列表末尾添加值为 value 的元素,可以传多个 name:键名;values:值 redis.rpush(‘list’, 1, 2, 3) 向键名为 list 的列表尾添加 1、2、3 3,列表大小 lpush(name, *values) 在键名为 name 的列表头添加值为 value 的元素,可以传多个 name:键名;values:值 redis.lpush(‘list’, 0) 向键名为 list 的列表头部添加 0 4,列表大小 llen(name) 返回键名为 name 的列表的长度 name:键名 redis.llen(‘list’) 返回键名为 list 的列表的长度 4 lrange(name, start, end) 返回键名为 name 的列表中 start 至 end 之间的元素 name:键名;start:起始索引;end:终止索引 redis.lrange(‘list’, 1, 3) 返回起始索引为 1 终止索引为 3 的索引范围对应的列表 [b’3’, b’2’, b’1’] ltrim(name, start, end) 截取键名为 name 的列表,保留索引为 start 到 end 的内容 name:键名;start:起始索引;end:终止索引 ltrim(‘list’, 1, 3) 保留键名为 list 的索引为 1 到 3 的元素 True lindex(name, index) 返回键名为 name 的列表中 index 位置的元素 name:键名;index:索引 redis.lindex(‘list’, 1) 返回键名为 list 的列表索引为 1 的元素 b’2’ lset(name, index, value) 给键名为 name 的列表中 index 位置的元素赋值,越界则报错 name:键名;index:索引位置;value:值 redis.lset(‘list’, 1, 5) 将键名为 list 的列表中索引为 1 的位置赋值为 5 True lrem(name, count, value) 删除 count 个键的列表中值为 value 的元素 name:键名;count:删除个数;value:值 redis.lrem(‘list’, 2, 3) 将键名为 list 的列表删除两个 3 1,即删除的个数 lpop(name) 返回并删除键名为 name 的列表中的首元素 name:键名 redis.lpop(‘list’) 返回并删除名为 list 的列表中的第一个元素 b’5’ rpop(name) 返回并删除键名为 name 的列表中的尾元素 name:键名 redis.rpop(‘list’) 返回并删除名为 list 的列表中的最后一个元素 b’2’ blpop(keys, timeout=0) 返回并删除名称在 keys 中的 list 中的首个元素,如果列表为空,则会一直阻塞等待 keys:键名序列;timeout:超时等待时间,0 为一直等待 redis.blpop(‘list’) 返回并删除键名为 list 的列表中的第一个元素 [b’5’] brpop(keys, timeout=0) 返回并删除键名为 name 的列表中的尾元素,如果 list 为空,则会一直阻塞等待 keys:键名序列;timeout:超时等待时间,0 为一直等待 redis.brpop(‘list’) 返回并删除名为 list 的列表中的最后一个元素 [b’2’] rpoplpush(src, dst) 返回并删除名称为 src 的列表的尾元素,并将该元素添加到名称为 dst 的列表头部 src:源列表的键;dst:目标列表的 key redis.rpoplpush(‘list’, ‘list2’) 将键名为 list 的列表尾元素删除并将其添加到键名为 list2 的列表头部,然后返回 b’2’ 【16.7】Set(集合)操作 方法 作用 参数说明 示例 示例说明 示例结果 sadd(name, *values) 向键名为 name 的集合中添加元素 name:键名;values:值,可为多个 redis.sadd(‘tags’, ‘Book’, ‘Tea’, ‘Coffee’) 向键名为 tags 的集合中添加 Book、Tea 和 Coffee 这 3 个内容 3,即插入的数据个数 srem(name, *values) 从键名为 name 的集合中删除元素 name:键名;values:值,可为多个 redis.srem(‘tags’, ‘Book’) 从键名为 tags 的集合中删除 Book 1,即删除的数据个数 spop(name) 随机返回并删除键名为 name 的集合中的一个元素 name:键名 redis.spop(‘tags’) 从键名为 tags 的集合中随机删除并返回该元素 b’Tea’ smove(src, dst, value) 从 src 对应的集合中移除元素并将其添加到 dst 对应的集合中 src:源集合;dst:目标集合;value:元素值 redis.smove(‘tags’, ‘tags2’, ‘Coffee’) 从键名为 tags 的集合中删除元素 Coffee 并将其添加到键为 tags2 的集合 True scard(name) 返回键名为 name 的集合的元素个数 name:键名 redis.scard(‘tags’) 获取键名为 tags 的集合中的元素个数 3 sismember(name, value) 测试 member 是否是键名为 name 的集合的元素 name:键值 redis.sismember(‘tags’, ‘Book’) 判断 Book 是否是键名为 tags 的集合元素 True sinter(keys, *args) 返回所有给定键的集合的交集 keys:键名序列 redis.sinter([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的交集 {b’Coffee’} sinterstore(dest, keys, *args) 求交集并将交集保存到 dest 的集合 dest:结果集合;keys:键名序列 redis.sinterstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的交集并将其保存为 inttag 1 sunion(keys, *args) 返回所有给定键的集合的并集 keys:键名序列 redis.sunion([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的并集 {b’Coffee’, b’Book’, b’Pen’} sunionstore(dest, keys, *args) 求并集并将并集保存到 dest 的集合 dest:结果集合;keys:键名序列 redis.sunionstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的并集并将其保存为 inttag 3 sdiff(keys, *args) 返回所有给定键的集合的差集 keys:键名序列 redis.sdiff([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的差集 {b’Book’, b’Pen’} sdiffstore(dest, keys, *args) 求差集并将差集保存到 dest 集合 dest:结果集合;keys:键名序列 redis.sdiffstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的差集并将其保存为 inttag 3 smembers(name) 返回键名为 name 的集合的所有元素 name:键名 redis.smembers(‘tags’) 返回键名为 tags 的集合的所有元素 {b’Pen’, b’Book’, b’Coffee’} srandmember(name) 随机返回键名为 name 的集合中的一个元素,但不删除元素 name:键值 redis.srandmember(‘tags’) 随机返回键名为 tags 的集合中的一个元素 Srandmember (name) 【16.8】SortedSet(有序集合)操作 方法 作用 参数说明 示例 示例说明 示例结果 zadd(name, args, *kwargs) 向键名为 name 的 zset 中添加元素 member,score 用于排序。如果该元素存在,则更新其顺序 name:键名;args:可变参数 redis.zadd(‘grade’, 100, ‘Bob’, 98, ‘Mike’) 向键名为 grade 的 zset 中添加 Bob(其 score 为 100),并添加 Mike(其 score 为 98) 2,即添加的元素个数 zrem(name, *values) 删除键名为 name 的 zset 中的元素 name:键名;values:元素 redis.zrem(‘grade’, ‘Mike’) 从键名为 grade 的 zset 中删除 Mike 1,即删除的元素个数 zincrby(name, value, amount=1) 如果在键名为 name 的 zset 中已经存在元素 value,则将该元素的 score 增加 amount;否则向该集合中添加该元素,其 score 的值为 amount name:键名;value:元素;amount:增长的 score 值 redis.zincrby(‘grade’, ‘Bob’, -2) 键名为 grade 的 zset 中 Bob 的 score 减 2 98.0,即修改后的值 zrank(name, value) 返回键名为 name 的 zset 中元素的排名,按 score 从小到大排序,即名次 name:键名;value:元素值 redis.zrank(‘grade’, ‘Amy’) 得到键名为 grade 的 zset 中 Amy 的排名 1 zrevrank(name, value) 返回键为 name 的 zset 中元素的倒数排名(按 score 从大到小排序),即名次 name:键名;value:元素值 redis.zrevrank (‘grade’, ‘Amy’) 得到键名为 grade 的 zset 中 Amy 的倒数排名 2 zrevrange(name, start, end, withscores= False) 返回键名为 name 的 zset(按 score 从大到小排序)中 index 从 start 到 end 的所有元素 name:键值;start:开始索引;end:结束索引;withscores:是否带 score redis.zrevrange (‘grade’, 0, 3) 返回键名为 grade 的 zset 中前四名元素 [b’Bob’, b’Mike’, b’Amy’, b’James’] zrangebyscore (name, min, max, start=None, num=None, withscores=False) 返回键名为 name 的 zset 中 score 在给定区间的元素 name:键名;min:最低 score;max:最高 score;start:起始索引;num:个数;withscores:是否带 score redis.zrangebyscore (‘grade’, 80, 95) 返回键名为 grade 的 zset 中 score 在 80 和 95 之间的元素 [b’Bob’, b’Mike’, b’Amy’, b’James’] zcount(name, min, max) 返回键名为 name 的 zset 中 score 在给定区间的数量 name:键名;min:最低 score;max:最高 score redis.zcount(‘grade’, 80, 95) 返回键名为 grade 的 zset 中 score 在 80 到 95 的元素个数 2 zcard(name) 返回键名为 name 的 zset 的元素个数 name:键名 redis.zcard(‘grade’) 获取键名为 grade 的 zset 中元素的个数 3 zremrangebyrank (name, min, max) 删除键名为 name 的 zset 中排名在给定区间的元素 name:键名;min:最低位次;max:最高位次 redis.zremrangebyrank (‘grade’, 0, 0) 删除键名为 grade 的 zset 中排名第一的元素 1,即删除的元素个数 zremrangebyscore (name, min, max) 删除键名为 name 的 zset 中 score 在给定区间的元素 name:键名;min:最低 score;max:最高 score redis.zremrangebyscore (‘grade’, 80, 90) 删除 score 在 80 到 90 之间的元素 1,即删除的元素个数 【16.9】RedisDumpRedisDump 是 Redis 一个数据导入导出工具,是基于 Ruby 实现的,首先访问 Ruby 官网安装对应操作系统的 Ruby:http://www.ruby-lang.org/zh_cn/downloads/ ,安装完成即可使用 gem 命令,该命令类似于 Python 当中的 pip 命令,使用 gem install redis-dump 即可完成 RedisDump 的安装,安装完成后就可以使用导出数据 redis-dump 命令和导入数据 redis-load 命令了 【16.9.1】导出数据 redis-dump在命令行输入 redis-dump -h 可以查看: 123456789101112Usage: E:/Ruby26-x64/bin/redis-dump [global options] COMMAND [command options] -u, --uri=S Redis URI (e.g. redis://hostname[:port]) -d, --database=S Redis database (e.g. -d 15) -a, --password=S Redis password (e.g. -a 'my@pass/word') -s, --sleep=S Sleep for S seconds after dumping (for debugging) -c, --count=S Chunk size (default: 10000) -f, --filter=S Filter selected keys (passed directly to redis' KEYS command) -b, --base64 Encode key values as base64 (useful for binary values) -O, --without_optimizations Disable run time optimizations -V, --version Display version -D, --debug --nosafe 命令解释: -u Redis 连接字符串 -d 数据库代号 -a 数据库密码 -s 导出之后的休眠时间 -c 分块大小,默认是 10000 -f 导出时的过滤器 -b 将键值编码为 base64(对二进制值有用) -O 禁用运行时优化 -V 显示版本 -D 开启调试 导出数据示例: 12345678910111213redis-dump# 指定端口redis-dump -u 127.0.0.1:6379# 指定端口和密码redis-dump -u :password@127.0.0.1:6379# 导出指定数据库redis-dump -u 127.0.0.1:6379 -d 3# 导出包含特定值的数据redis-dump -u 127.0.0.1:6379 -f age 输出示例: 1234567891011121314151617181920212223# 导出所有数据{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2}# 导出 3 号数据库{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}# 导出 key 包含 age 的数据{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 导出所有数据为 JSON 文件: 1redis-dump -u 127.0.0.1:6379 > db_full.json 该命令将会在当前目录生成一个名为 db_full.json 的文件,文件内容如下: 1234567891011121314{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 使用参数 -d 指定某个数据库的所有数据导出为 JSON 文件: 1redis-dump -u 127.0.0.1:6379 -d 4 > db_db4.json 该命令会将 4 号数据库的数据导出到 db_db4.json 文件: 12{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 使用参数 -f 过滤数据,只导出特定的数据: 1redis-dump -u 127.0.0.1:6379 -f name > db_name.json 该命令会导出 key 包含 name 的数据到 db_name.json 文件: 123456789101112{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3} 【16.9.2】导入数据 redis-load在命令行输入 redis-load -h 可以查看: 123456789redis-load --help Try: redis-load [global options] COMMAND [command options] -u, --uri=S Redis URI (e.g. redis://hostname[:port]) -d, --database=S Redis database (e.g. -d 15) -s, --sleep=S Sleep for S seconds after dumping (for debugging) -n, --no_check_utf8 -V, --version Display version -D, --debug --nosafe 命令解释: -u Redis 连接字符串 -d 数据库代号,默认是全部 -s 导出之后的休眠时间 -n 不检测 UTF-8 编码 -V 显示版本 -D 开启调试 导入示例: 12345# 将 test.json 文件所有内容导入到数据库< test.json redis-load -u 127.0.0.1:6379# 将 test.json 文件 db 值为 6 的数据导入到数据库 < test.json redis-load -u 127.0.0.1:6379 -d 6 另外,以下方法也能导入数据: 12345# 将 test.json 文件所有内容导入到数据库cat test.json | redis-load -u 127.0.0.1:6379# 将 test.json 文件 db 值为 6 的数据导入到数据库 cat test.json | redis-load -u 127.0.0.1:6379 -d 6 注意:cat 是 Linux 系统专有的命令,在 Windows 系统里没有 cat 这个命令,可以使用 Windows 批处理命令 type 代替 cat","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Redis","slug":"Redis","permalink":"https://www.itrhx.com/tags/Redis/"}]},{"title":"Python3 爬虫学习笔记 C15","slug":"A45-Python3-spider-C15","date":"2019-09-10T11:46:13.293Z","updated":"2019-09-24T12:41:02.822Z","comments":true,"path":"2019/09/10/A45-Python3-spider-C15/","link":"","permalink":"https://www.itrhx.com/2019/09/10/A45-Python3-spider-C15/","excerpt":"Python3 爬虫学习笔记第十五章 —— 【代理的基本使用】","text":"Python3 爬虫学习笔记第十五章 —— 【代理的基本使用】 【15.1】代理初识大多数网站都有反爬虫机制,如果一段时间内同一个 IP 发送的请求过多,服务器就会拒绝访问,直接禁封该 IP,此时,设置代理即可解决这个问题,网络上有许多免费代理和付费代理,比如西刺代理,全网代理 IP,快代理等,设置代理需要用到的就是代理 IP 地址和端口号,如果电脑上装有代理软件(例如:酸酸乳SSR),软件一般会在本机创建 HTTP 或 SOCKS 代理服务,直接使用此代理也可以 【15.2】urllib 库使用代理1234567891011121314from urllib.error import URLErrorfrom urllib.request import ProxyHandler, build_openerproxy = '127.0.0.1:1080'proxy_handler = ProxyHandler({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})opener = build_opener(proxy_handler)try: response = opener.open('http://httpbin.org/get') print(response.read().decode('utf8'))except URLError as e: print(e.reason) http://httpbin.org/get 是一个请求测试站点,借助 ProxyHandler 设置代理,参数为字典类型,键名为协议类型,键值为代理,代理的写法:proxy = '127.0.0.1:1080',其中 127.0.0.1 为 IP 地址,1080 为端口号,这里表示本机的代理软件已经在本地 1080 端口创建了代理服务,代理前面需要加上 http 或者 https 协议,当请求的链接为 http 协议时,ProxyHandler 会自动调用 http 代理,同理,当请求的链接为 https 协议时,ProxyHandler 会自动调用 https 代理,build_opener() 方法传入 ProxyHandler 对象来创建一个 opener,调用 open() 方法传入一个 url 即可通过代理访问该链接,运行结果为一个 JSON,origin 字段为此时客户端的 IP 12345678910{ \"args\": {}, \"headers\": { \"Accept-Encoding\": \"identity\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Python-urllib/3.6\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 如果是需要认证的代理,只需要在代理前面加入代理认证的用户名密码即可: 1234567891011121314from urllib.error import URLErrorfrom urllib.request import ProxyHandler, build_openerproxy = 'username:password@127.0.0.1:1080'proxy_handler = ProxyHandler({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})opener = build_opener(proxy_handler)try: response = opener.open('http://httpbin.org/get') print(response.read().decode('utf8'))except URLError as e: print(e.reason) 如果代理是 SOCKS5 类型,需要用到 socks 模块,设置代理方法如下: 扩展:SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问 Internet 网中的服务器,或者使通讯更加安全 123456789101112import socksimport socketfrom urllib import requestfrom urllib.error import URLErrorsocks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)socket.socket = socks.socksockettry: response = request.urlopen('http://httpbin.org/get') print(response.read().decode('utf-8'))except URLError as e: print(e.reason) 【15.3】requests 库使用代理requests 库使用代理只需要传入 proxies 参数即可: 123456789101112import requestsproxy = '127.0.0.1:1080'proxies = ({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ChunkedEncodingError as e: print('Error', e.args) 输出结果: 1234567891011{ \"args\": {}, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 同样的,如果是需要认证的代理,也只需要在代理前面加入代理认证的用户名密码即可: 123456789101112import requestsproxy = 'username:password@127.0.0.1:1080'proxies = ({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ChunkedEncodingError as e: print('Error', e.args) 如果代理是 SOCKS5 类型,需要用到 requests[socks] 模块或者 socks 模块,使用 requests[socks] 模块时设置代理方法如下: 123456789101112import requestsproxy = '127.0.0.1:1080'proxies = { 'http': 'socks5://' + proxy, 'https': 'socks5://' + proxy}try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ConnectionError as e: print('Error', e.args) 使用 socks 模块时设置代理方法如下(此类方法为全局设置): 1234567891011import requestsimport socksimport socketsocks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)socket.socket = socks.socksockettry: response = requests.get('http://httpbin.org/get') print(response.text)except requests.exceptions.ConnectionError as e: print('Error', e.args) 【15.4】Selenium 使用代理【15.4.1】Chrome12345678from selenium import webdriverproxy = '127.0.0.1:1080'chrome_options = webdriver.ChromeOptions()chrome_options.add_argument('--proxy-server=http://' + proxy)path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://httpbin.org/get') 通过 ChromeOptions 来设置代理,在创建 Chrome 对象的时候用 chrome_options 参数传递即可,访问目标链接后显示如下信息: 12345678910111213{ \"args\": {}, \"headers\": { \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,zh;q=0.9\", \"Host\": \"httpbin.org\", \"Upgrade-Insecure-Requests\": \"1\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 如果是认证代理,则设置方法如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsimport zipfileip = '127.0.0.1'port = 1080username = 'username'password = 'password'manifest_json = \"\"\"{\"version\":\"1.0.0\",\"manifest_version\": 2,\"name\":\"Chrome Proxy\",\"permissions\": [\"proxy\",\"tabs\",\"unlimitedStorage\",\"storage\",\"<all_urls>\",\"webRequest\",\"webRequestBlocking\"],\"background\": {\"scripts\": [\"background.js\"] }}\"\"\"background_js =\"\"\"var config = { mode: \"fixed_servers\", rules: { singleProxy: { scheme: \"http\", host: \"%(ip) s\", port: %(port) s } } }chrome.proxy.settings.set({value: config, scope: \"regular\"}, function() {});function callbackFn(details) { return { authCredentials: {username: \"%(username) s\", password: \"%(password) s\" } }}chrome.webRequest.onAuthRequired.addListener( callbackFn, {urls: [\"<all_urls>\"]}, ['blocking'])\"\"\" % {'ip': ip, 'port': port, 'username': username, 'password': password}plugin_file = 'proxy_auth_plugin.zip'with zipfile.ZipFile(plugin_file, 'w') as zp: zp.writestr(\"manifest.json\", manifest_json) zp.writestr(\"background.js\", background_js)chrome_options = Options()chrome_options.add_argument(\"--start-maximized\")path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'chrome_options.add_extension(plugin_file)browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://httpbin.org/get') 需要在本地创建一个 manifest.json 配置文件和 background.js 脚本来设置认证代理。运行代码之后本地会生成一个 proxy_auth_plugin.zip 文件来保存当前配置 【15.4.1】PhantomJS借助 service_args 参数,也就是命令行参数即可设置代理: 12345678910from selenium import webdriverservice_args = [ '--proxy=127.0.0.1:1080', '--proxy-type=http']path = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)browser.get('http://httpbin.org/get')print(browser.page_source) 运行结果: 12345678910111213<html><head></head><body><pre style=\"word-wrap: break-word; white-space: pre-wrap;\">{ \"args\": {}, \"headers\": { \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,en,*\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"}</pre></body></html> 如果是需要认证的代理,只需要在 service_args 参数加入 –proxy-auth 选项即可: 1234567891011from selenium import webdriverservice_args = [ '--proxy=127.0.0.1:1080', '--proxy-type=http', '--proxy-auth=username:password']path = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)browser.get('http://httpbin.org/get')print(browser.page_source)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"代理","slug":"代理","permalink":"https://www.itrhx.com/tags/代理/"}]},{"title":"Python3 爬虫学习笔记 C14","slug":"A44-Python3-spider-C14","date":"2019-09-07T17:38:41.491Z","updated":"2019-09-24T12:43:31.445Z","comments":true,"path":"2019/09/08/A44-Python3-spider-C14/","link":"","permalink":"https://www.itrhx.com/2019/09/08/A44-Python3-spider-C14/","excerpt":"Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】","text":"Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】 【14.1】关于点触验证码点触验证码是由杭州微触科技有限公司研发的新一代的互联网验证码,使用点击的形式完成验证,采用专利的印刷算法以及加密算法,保证每次请求到的验证图具有极高的安全性,常见的点触验证码如下: 【14.2】点触验证码攻克思路点触验证码相对其他类型验证码比较复杂,如果依靠 OCR 图像识别点触验证码,则识别难度非常大,此时就要用到互联网的验证码服务平台,这些服务平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多 以下以超级鹰打码平台和中国铁路12306官网来做练习 【14.3】模拟登录 12306 — 总体思路首先在超级鹰打码平台注册账号并申请一个软件 ID,官网:http://www.chaojiying.com/ ,先充值一块钱得到 1000 题分,观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1~4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 获取其 Python API:http://www.chaojiying.com/download/Chaojiying_Python.rar ,然后用 Selenium 模拟登陆,获取到验证码,并将验证码发送给超级鹰后台,返回识别图片的坐标,最后模拟点击即可,整个过程的实现由主程序 12306.py 和超级鹰 API chaojiying.py 组成 整个程序包含的函数: 12345678910def __init__(): 初始化 WebDriver、Chaojiying 对象等def crack(): 破解入口、获取、识别验证码、模拟登录def open(): 账号密码输入def get_screenshot(): 整个页面截图def get_touclick_element(): 获取验证码位置def get_position(): 获取验证码坐标def get_touclick_image(): 剪裁验证码部分def get_points(self, captcha_result): 分析超级鹰返回的坐标def touch_click_words(self, locations): 模拟点击符合要求的图片def login(self): 点击登陆按钮,完成模拟登录 整个程序用到的库: 1234567891011import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import Chaojiyingfrom selenium.common.exceptions import TimeoutException 【14.4】主函数123if __name__ == '__main__': crack = CrackTouClick() crack.crack() 【14.5】初始化函数1234567891011121314151617181920USERNAME = '155********'PASSWORD = '***********'CHAOJIYING_USERNAME = '*******'CHAOJIYING_PASSWORD = '*******'CHAOJIYING_SOFT_ID = '********'CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.email = USERNAME self.password = PASSWORD self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) 定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录链接 url:https://kyfw.12306.cn/otn/resources/login.html ,谷歌浏览器驱动的目录(path),浏览器启动参数,并将相关参数传递给超级鹰 API 【14.6】破解入口函数123456789101112131415161718def crack(self): self.open() image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) locations = self.get_points(result) self.touch_click_words(locations) self.login() try: success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '用户姓名')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print(cc.text) except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() 调用 open() 函数输入账号密码 调用 get_touclick_image() 函数获取验证码图片 利用超级鹰 Python API PostPic() 方法即可把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个 JSON,如果识别成功,典型的返回结果类似于:{'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': '1f8e1d4bef8b11484cb1f1f34299865b'},其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔 调用 get_points() 函数解析超级鹰识别结果 调用 touch_click_words() 函数对符合要求的图片进行点击,然后点击登陆按钮模拟登陆 使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值 【14.7】账号密码输入函数123456789def open(self): self.browser.get(self.url) login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) username.send_keys(self.email) password.send_keys(self.password) 分析页面可知,登陆页面 URL 为:https://kyfw.12306.cn/otn/resources/login.html ,该页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userName 和 J-password,调用 send_keys() 方法输入账号密码 【14.8】页面截图函数1234def get_screenshot(self): screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot 对整个页面进行截图 【14.9】验证码元素查找函数123def get_touclick_element(self): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) return element 同样分析页面,验证码所在位置的 CSS 为 login-pwd-code 【14.10】获取验证码坐标函数1234567def get_position(self): element = self.get_touclick_element() time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] return (top, bottom, left, right) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 【14.11】验证码剪裁函数123456def get_touclick_image(self, name='12306.png'): top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha 根据验证码的坐标信息,对页面截图进行剪裁,得到验证码部分,将其保存为 12306.png 【14.12】验证码坐标解析函数1234def get_points(self, captcha_result): groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations get_points() 方法将超级鹰的验证码识别结果变成列表的形式 【14.13】验证码模拟点击函数1234def touch_click_words(self, locations): for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform() touch_click_words() 方法通过调用 move_to_element_with_offset() 方法依次传入解析后的坐标,点击即可 【14.14】模拟点击登陆函数123def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click() 分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录 【14.15】效果实现动图最终实现效果图:(关键信息已经过打码处理) 【14.16】完整代码12306.py 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import Chaojiying_Clientfrom selenium.common.exceptions import TimeoutExceptionUSERNAME = '155********'PASSWORD = '***********'CHAOJIYING_USERNAME = '***********'CHAOJIYING_PASSWORD = '***********'CHAOJIYING_SOFT_ID = '******'CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): #登陆 self.url = 'https://kyfw.12306.cn/otn/resources/login.html' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.email = USERNAME self.password = PASSWORD self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) def crack(self): self.open() image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) locations = self.get_points(result) self.touch_click_words(locations) self.login() try: success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭仁侯')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print(cc.text) except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() def open(self): self.browser.get(self.url) login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) username.send_keys(self.email) password.send_keys(self.password) def get_screenshot(self): screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_touclick_element(self): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) return element def get_position(self): element = self.get_touclick_element() time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] return (top, bottom, left, right) def get_touclick_image(self, name='12306.png'): top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def get_points(self, captcha_result): groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations def touch_click_words(self, locations): for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform() def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click()if __name__ == '__main__': crack = CrackTouClick() crack.crack() chaojiying.py 1234567891011121314151617181920212223242526272829303132333435363738394041424344import requestsfrom hashlib import md5class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): \"\"\" im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html \"\"\" params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): \"\"\" im_id:报错题目的图片ID \"\"\" params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"点触验证码","slug":"点触验证码","permalink":"https://www.itrhx.com/tags/点触验证码/"}]},{"title":"Python3 爬虫学习笔记 C13","slug":"A43-Python3-spider-C13","date":"2019-09-06T19:52:14.161Z","updated":"2019-09-24T12:40:56.234Z","comments":true,"path":"2019/09/07/A43-Python3-spider-C13/","link":"","permalink":"https://www.itrhx.com/2019/09/07/A43-Python3-spider-C13/","excerpt":"Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】","text":"Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】 【13.1】关于滑动验证码滑动验证码属于行为式验证码,需要通过用户的操作行为来完成验证,一般是根据提示用鼠标将滑块拖动到指定的位置完成验证,此类验证码背景图片采用多种图像加密技术,且添加了很多随机效果,能有效防止OCR文字识别,另外,验证码上的文字采用了随机印刷技术,能够随机采用多种字体、多种变形的实时随机印刷,防止暴力破解;斗鱼、哔哩哔哩、淘宝等平台都使用了滑动验证码 【13.2】滑动验证码攻克思路利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证,首先要分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片,通过对比原始的图片和带滑块缺口的图片的像素,像素不同的地方就是缺口位置,计算出滑块缺口的位置,得到所需要滑动的距离,最后利用 Selenium 进行对滑块的拖拽,拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功 以下以哔哩哔哩为例来做模拟登录练习 【13.3】模拟登录 bilibili — 总体思路首先使用 Selenium 模拟登陆 bilibili,自动输入账号密码,查找到登陆按钮并点击,使其出现滑动验证码,此时分析页面,滑动验证组件是由3个 canvas 组成,分别代表完整图片、带有缺口的图片和需要滑动的图片,3个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,分别获取三张图片时要将其他两张图片设置为 display:none,获取元素位置后即可对图片截图并保存,通过图片像素对比,找到缺口位置即为滑块要移动的距离,随后构造滑动轨迹,按照先加速后减速的方式移动滑块完成验证。 整个程序包含的函数: 1234567891011def init(): 初始化函数,定义全局变量def login(): 登录函数,输入账号密码并点击登录def find_element(): 验证码元素查找函数,查找三张图的元素def hide_element(): 设置元素不可见函数def show_element(): 设置元素可见函数def save_screenshot(): 验证码截图函数,截取三张图并保存def slide(): 滑动函数def is_pixel_equal(): 像素判断函数,寻找缺口位置def get_distance(): 计算滑块移动距离函数def get_track(): 构造移动轨迹函数def move_to_gap(): 模拟拖动函数 整个程序用到的库: 12345678from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport random 【13.4】主函数12345if __name__ == '__main__': init() login() find_element() slide() 【13.5】初始化函数12345678910def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) username = '155********' password = '***********' wait = WebDriverWait(browser, 20) global 关键字定义了全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时 【13.6】登录函数123456789def login(): browser.get(url) user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) user.send_keys(username) passwd.send_keys(password) login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) time.sleep(random.random() * 3) login_btn.click() 等待用户名输入框和密码输入框对应的 ID 节点加载出来,分析页面可知,用户名输入框 id="login-username",密码输入框 id="login-passwd",获取这两个节点,调用 send_keys() 方法输入用户名和密码,随后获取登录按钮,分析页面可知登录按钮 class="btn btn-login",随机产生一个数并将其扩大三倍作为暂停时间,最后调用 click() 方法实现登录按钮的点击 【13.7】验证码元素查找函数12345678910111213def find_element(): c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) hide_element(c_slice) save_screenshot(c_background, 'back') show_element(c_slice) save_screenshot(c_slice, 'slice') show_element(c_full_bg) save_screenshot(c_full_bg, 'full') 我们要获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片,分析页面代码,这三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片,定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute,随后传值给 save_screenshot() 函数,进一步对验证码进行处理 【13.8】元素可见性设置函数12345678# 设置元素不可见def hide_element(element): browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")# 设置元素可见def show_element(element): browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;") 【13.9】验证码截图函数1234567891011121314151617181920def save_screenshot(obj, name): try: pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息,首先调用 save_screenshot() 属性对整个页面截图并保存,然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存 【13.10】滑动函数123456def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3) 向 get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证 【13.11】计算滑块移动距离函数123456def get_distance(bg_image, fullbg_image): distance = 60 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): if not is_pixel_equal(fullbg_image, bg_image, i, j): return i get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 像素判断函数判断两张图片同一位置的像素是否相同,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置 【13.12】像素判断函数123456789def is_pixel_equal(bg_image, fullbg_image, x, y): bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] threshold = 60 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False 将完整图片和缺口图片两个对象分别赋值给变量 bg_image和 fullbg_image,接下来对比图片获取缺口。我们在这里遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,如果二者的 RGB 数据差距在一定范围内,那就代表两个像素相同,继续比对下一个像素点,如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置 【13.13】构造移动轨迹函数123456789101112131415def get_trace(distance): trace = [] faster_distance = distance * (4 / 5) start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 20 else: a = -20 move = v0 * t + 1 / 2 * a * t * t v = v0 + a * t v0 = v start += move trace.append(round(move)) return trace get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系: 12move = v0 * t + 0.5 * a * t * t v = v0 + a * t 设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为20和-20,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了 【13.14】模拟拖动函数1234567def move_to_gap(trace): slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) ActionChains(browser).click_and_hold(slider).perform() for x in trace: ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(browser).release().perform() 传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可 【13.15】效果实现动图最终实现效果图:(关键信息已经过打码处理) 【13.16】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport randomfrom PIL import Imagedef init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) username = '155********' password = '***********' wait = WebDriverWait(browser, 20)def login(): browser.get(url) user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) user.send_keys(username) passwd.send_keys(password) login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) time.sleep(random.random() * 3) login_btn.click()def find_element(): c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) hide_element(c_slice) save_screenshot(c_background, 'back') show_element(c_slice) save_screenshot(c_slice, 'slice') show_element(c_full_bg) save_screenshot(c_full_bg, 'full')def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\")def save_screenshot(obj, name): try: pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg)def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3)def get_distance(bg_image, fullbg_image): distance = 60 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): if not is_pixel_equal(fullbg_image, bg_image, i, j): return idef is_pixel_equal(bg_image, fullbg_image, x, y): bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] threshold = 60 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return Falsedef get_trace(distance): trace = [] faster_distance = distance * (4 / 5) start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 20 else: a = -20 move = v0 * t + 1 / 2 * a * t * t v = v0 + a * t v0 = v start += move trace.append(round(move)) return tracedef move_to_gap(trace): slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) ActionChains(browser).click_and_hold(slider).perform() for x in trace: ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(browser).release().perform()if __name__ == '__main__': init() login() find_element() slide()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"滑动验证码","slug":"滑动验证码","permalink":"https://www.itrhx.com/tags/滑动验证码/"}]},{"title":"Python3 爬虫学习笔记 C12","slug":"A42-Python3-spider-C12","date":"2019-09-05T14:54:48.887Z","updated":"2019-09-24T12:40:54.127Z","comments":true,"path":"2019/09/05/A42-Python3-spider-C12/","link":"","permalink":"https://www.itrhx.com/2019/09/05/A42-Python3-spider-C12/","excerpt":"Python3 爬虫学习笔记第十二章 —— 【验证码对抗系列 — 图形验证码】","text":"Python3 爬虫学习笔记第十二章 —— 【验证码对抗系列 — 图形验证码】 【12.1】关于普通图形验证码普通图形验证码一般由四位纯数字、纯字母或者字母数字组合构成,是最常见的验证码,也是最简单的验证码,利用 tesserocr 或者 pytesseract 库即可识别此类验证码,前提是已经安装好 Tesseract-OCR 软件 【12.2】tesserocr 库识别验证码简单示例: 123456import tesserocrfrom PIL import Imageimage = Image.open('code.png')result = tesserocr.image_to_text(image)print(result) 新建一个 Image 对象,调用 tesserocr 的 image_to_text() 方法,传入 Image 对象即可完成识别,另一种方法: 12import tesserocrprint(tesserocr.file_to_text('code.png')) 【12.3】pytesseract 库识别验证码简单示例: 1234567import pytesseractfrom PIL import Imageimg = Image.open('code.png')img = img.convert('RGB')img.show()print(pytesseract.image_to_string(img)) pytesseract 的各种方法: get_tesseract_version:返回 Tesseract 的版本信息; image_to_string:将图像上的 Tesseract OCR 运行结果返回到字符串; image_to_boxes:返回包含已识别字符及其框边界的结果; image_to_data:返回包含框边界,置信度和其他信息的结果。需要 Tesseract 3.05+; image_to_osd:返回包含有关方向和脚本检测的信息的结果。 有关参数: image_to_data(image, lang='', config='', nice=0, output_type=Output.STRING) image:图像对象; lang:Tesseract 语言代码字符串; config:任何其他配置为字符串,例如:config=’–psm 6’; nice:修改 Tesseract 运行的处理器优先级。Windows不支持。尼斯调整了类似 unix 的流程的优点; output_type:类属性,指定输出的类型,默认为string。 lang 参数,常见语言代码如下: chi_sim:简体中文 chi_tra:繁体中文 eng:英文 rus:俄罗斯语 fra:法语 deu:德语 jpn:日语 【12.4】验证码处理利用 Image 对象的 convert() 方法传入不同参数可以对验证码做一些额外的处理,如转灰度、二值化等操作,经过处理过后的验证码会更加容易被识别,识别准确度更高,各种参数及含义: 1:1位像素,黑白,每字节一个像素存储; L:8位像素,黑白; P:8位像素,使用调色板映射到任何其他模式; RGB:3x8位像素,真彩色; RGBA:4x8位像素,带透明度掩模的真彩色; CMYK:4x8位像素,分色; YCbCr:3x8位像素,彩色视频格式; I:32位有符号整数像素; F:32位浮点像素。 示例: 12345678import pytesseractfrom PIL import Imageimage = Image.open('code.png')image = image.convert('L')image.show()result = pytesseract.image_to_string(image)print(result) Image 对象的 convert() 方法参数传入 L,即可将图片转化为灰度图像,转换前后对比: 12345678import pytesseractfrom PIL import Imageimage = Image.open('code.png')image = image.convert('1')image.show()result = pytesseract.image_to_string(image)print(result) Image 对象的 convert() 方法参数传入 1,即可将图片进行二值化处理,处理前后对比: 【12.5】tesserocr 与 pytesserocr 相关资料 tesserocr GitHub:https://github.com/sirfz/tesserocr tesserocr PyPI:https://pypi.python.org/pypi/tesserocr pytesserocr GitHub:https://github.com/madmaze/pytesseract pytesserocr PyPI:https://pypi.org/project/pytesseract/ Tesseract-OCR 下载地址:http://digi.bib.uni-mannheim.de/tesseract tesseract GitHub:https://github.com/tesseract-ocr/tesseract tesseract 语言包:https://github.com/tesseract-ocr/tessdata tesseract 文档:https://github.com/tesseract-ocr/tesseract/wiki/Documentation","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"图形验证码","slug":"图形验证码","permalink":"https://www.itrhx.com/tags/图形验证码/"}]},{"title":"Python3 爬虫学习笔记 C11","slug":"A41-Python3-spider-C11","date":"2019-09-04T14:06:03.110Z","updated":"2019-09-24T12:40:45.074Z","comments":true,"path":"2019/09/04/A41-Python3-spider-C11/","link":"","permalink":"https://www.itrhx.com/2019/09/04/A41-Python3-spider-C11/","excerpt":"Python3 爬虫学习笔记第十一章 —— 【MongoDB数据储存】","text":"Python3 爬虫学习笔记第十一章 —— 【MongoDB数据储存】 【11.1】关于 MongoDBMongoDB 属于非关系型数据库,即 NoSQL(Not Only SQL),NoSQL 是基于键值对的,不需要经过 SQL 层的解析,数据之间没有耦合性,性能极高,非关系型数据库分为以下几种: 键值存储数据库:Redis、Voldemort、Oracle BDB 等; 列存储数据库:Cassandra、HBase、Riak 等; 文档型数据库:CouchDB、MongoDB 等; 图形数据库:Neo4J、InfoGrid、Infinite Graph 等。 【11.2】MongoDB 基本操作语句123456789101112131415161718192021222324252627282930# 创建数据库(如果数据库不存在就创建数据库, 存在就切换到指定的数据库)use DATABASE_NAME# 查看所有数据库show dbs# 查看当前所在数据库db# 删除当前数据库db.dropDatabase()# 删除集合db.COLLECTION_NAME.drop()# 创建集合db.createCollection(\"COLLECTION_NAME\")# 插入文档db.COLLECTION_NAME.insert(document)db.COLLECTION_NAME.save(document) # 更新文档db.COLLECTION_NAME.update()# 删除文档db.COLLECTION_NAME.remove()# 查询文档db.COLLECTION_NAME.find(query, projection) 【11.3】连接 MongoDB连接 MongoDB 需要导入 pymongo 库,使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port 即可 123import pymongoclient = pymongo.MongoClient(host='localhost', port=27017) 也可以直接传入 MongoDB 的连接字符串: 123import pymongoclient = pymongo.MongoClient('mongodb://localhost:27017/') 【11.4】指定数据库使用以下语句皆可指定一个名为 spiders 的数据库: 1db = client.spiders 1db = client['spiders'] 【11.5】指定集合MongoDB 的每个数据库包含多个集合(collection),类似于关系型数据库 MySQL 中的数据表,使用以下语句皆可指定一个名为 students 的集合: 1collection = db.students 1collection = db['students'] 【11.6】插入数据12345678910111213import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}result = collection.insert(students)print(result) 在 spiders 数据库的 students 集合里,新建一条学生数据,该数据以字典形式表示,调用 collection 的 insert() 方法插入数据,在 MongoDB 中,每条数据都有一个_id 属性来唯一标识。如果没有显式指明该属性,MongoDB 会自动产生一个 ObjectId 类型的_id 属性。insert() 方法会在执行后返回 _id 值,在 MongoDB 数据库里面可以看到已经成功插入数据,输出结果: 15d6f1a4b57b65e1547bb3c24 进阶操作:同时插入多条数据,以列表形式传递: 12345678910111213141516171819import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents1 = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}students2 = { 'id': '17110106', 'name': 'AAAA', 'age': 22, 'gender': 'male'}result = collection.insert([students1, students2])print(result) 输出结果: 1[ObjectId('5d6f2be3cd1721962218a709'), ObjectId('5d6f2be3cd1721962218a70a')] PyMongo 3.x 及以上版本中,推荐使用 insert_one() 和 insert_many() 方法来分别插入单条记录和多条记录,示例: 插入单条记录 1234567891011121314import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}result = collection.insert_one(students)print(result)print(result.inserted_id) 返回的是 InsertOneResult 对象,调用其 inserted_id 属性获取_id: 12<pymongo.results.InsertOneResult object at 0x0000020ED91A5608>5d6f73940fe700c5a7ac19f0 插入多条记录 1234567891011121314151617181920import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents1 = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}students2 = { 'id': '17110106', 'name': 'AAAA', 'age': 22, 'gender': 'male'}result = collection.insert_many([students1, students2])print(result)print(result.inserted_ids) 返回的类型是 InsertManyResult,调用 inserted_ids 属性可以获取插入数据的_id 列表: 12<pymongo.results.InsertManyResult object at 0x0000021698DD36C8>[ObjectId('5d6f68598fa881c69b2e0006'), ObjectId('5d6f68598fa881c69b2e0007')] 【11.6】数据查询事先已经创建好 spiders 数据库和 students 集合,包含以下数据: 1234567891011121314151617181920212223_id:ObjectId(\"5d6f95d40828142f1dc35fa5\")id:\"17110105\"name:\"TRHX\"age:20gender:\"male\"_id:ObjectId(\"5d6f95d40828142f1dc35fa6\")id:\"17110106\"name:\"AAA\"age:20gender:\"male\"_id:ObjectId(\"5d6f95d40828142f1dc35fa7\")id:\"17110107\"name:\"BBB\"age:19gender:\"female\"_id:ObjectId(\"5d6f95d40828142f1dc35fa8\")id:\"17110108\"name:\"CCC\"age:22gender:\"male\" 查询方法一:利用 find_one() 或 find() 方法进行查询, find_one() 查询得到的是单个结果,find() 则返回一个生成器对象 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find_one({'name': 'TRHX'})print(result) 查询 name 为 TRHX 的数据,返回一个字典类型: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'} 查询方法二:根据 ObjectId 查询,查询时需要使用 bson 库里面的 objectid: 12345678import pymongofrom bson.objectid import ObjectIdclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find_one({'_id': ObjectId('5d6f95d40828142f1dc35fa7')})print(result) 查询结果: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa7'), 'id': '17110107', 'name': 'BBB', 'age': 19, 'gender': 'female'} 使用 find() 方法查询多条数据: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'gender': 'male'})print(results)for result in results: print(result) find() 方法返回一个生成器对象,遍历得到所有数据,每条数据都是字典类型: 1234<pymongo.cursor.Cursor object at 0x00000191F69AAA90>{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'} 在查询条件中加入比较符号进行查询,以下代码实现了年龄大于等于20的数据查询: 12345678import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'age': {'$gte': 20}})for result in results: print(result) 符号 $gte 表示大于等于,查询结果如下: 123{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'} 附表:各种比较符号 符号 含义 示例 $lt 小于 {‘age’: {‘$lt’: 20}} $gt 大于 {‘age’: {‘$gt’: 20}} $lte 小于等于 {‘age’: {‘$lte’: 20}} $gte 大于等于 {‘age’: {‘$gte’: 20}} $ne 不等于 {‘age’: {‘$ne’: 20}} $in 在范围内 {‘age’: {‘$in’: [20, 23]}} $nin 不在范围内 {‘age’: {‘$nin’: [20, 23]}} 在查询条件中加入功能符号进行查询,以下代码用正则匹配实现了对名字以 T 开头的学生数据的查询: 12345678import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'name': {'$regex': '^T.*'}})for result in results: print(result) 查询结果: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'} 附表:各种功能符号 符号 含义 示例 示例含义 $regex 匹配正则表达式 {‘name’: {‘$regex’: ‘^T.*’}} name 以 T 开头 $exists 属性是否存在 {‘name’: {‘$exists’: True}} name 属性存在 $type 类型判断 {‘age’: {‘$type’: ‘int’}} age 的类型为 int $mod 数字模操作 {‘age’: {‘$mod’: [5, 0]}} 年龄模 5 余 0 $text 文本查询 {‘$text’: {‘$search’: ‘Mike’}} text 类型的属性中包含 Mike 字符串 $where 高级条件查询 {‘$where’: ‘obj.fans_count == obj.follows_count’} 自身粉丝数等于关注数 其他操作:https://docs.mongodb.com/manual/reference/operator/query/ 【11.7】数据计数调用 count() 方法可以统计查询结果有多少条数据,输出结果为一个整数: 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find({'name': {'$regex': '^T.*'}}).count()print(result) 【11.8】数据排序调用 sort() 方法,向其传入排序的字段及升降序标志即可完成排序: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsascending = collection.find().sort('name', pymongo.ASCENDING)descending = collection.find().sort('name', pymongo.DESCENDING)print('升序排列:', [result['name'] for result in ascending])print('降序排列:', [result['name'] for result in descending]) 输出结果: 12升序排列: ['AAA', 'BBB', 'CCC', 'TRHX']降序排列: ['TRHX', 'CCC', 'BBB', 'AAA'] 【11.9】数据偏移利用 skip() 方法偏移几个位置,就可以跳过前几条数据,获取偏移量之后的几个数据;利用 limit() 方法指定获取前几条数据: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsascending = collection.find().sort('name', pymongo.ASCENDING).skip(1)descending = collection.find().sort('name', pymongo.DESCENDING).limit(2)print('升序排列(偏移量为1,获取后三条数据):', [result['name'] for result in ascending])print('降序排列(限制获取前两条数据):', [result['name'] for result in descending]) 输出结果: 12升序排列(偏移量为1,获取后三条数据): ['BBB', 'CCC', 'TRHX']降序排列(限制获取前两条数据): ['TRHX', 'CCC'] 【11.10】更新数据使用 update() 方法,指定更新的条件和更新后的数据即可: 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 18result = collection.update(condition, student)print(result) 该代码将 name 为 TRHX 的 age 改为了 18,返回结果仍然是字典形式,ok 代表执行成功,nModified 代表影响的数据条数: 1{'n': 1, 'nModified': 1, 'ok': 1.0, 'updatedExisting': True} 进阶操作:使用 $set 操作符对数据进行更新,指定更新的条件和更新后的数据即可,这样做的好处是:只更新指定的 student 字典内存在的字段,如果原先还有其他字段,则不会更新,也不会删除;如果不用 $set ,则会把之前的数据全部用 student 字典替换,如果原本存在其他字段,则会被删除 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 18result = collection.update(condition, {'$set': student})print(result) 和插入数据的 insert() 方法一样,在 PyMongo 3.x 版本里,推荐使用 update_one() 和 update_many() 方法 1234567891011import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 19result = collection.update_one(condition, {'$set': student})print(result)print(result.matched_count, result.modified_count) 注意:update_one() 方法不能直接传入修改后的字典,只能使用 {'$set': student} 的形式传入,可以调用 matched_count 和 modified_count 属性,获取匹配的数据条数和影响的数据条数: 12<pymongo.results.UpdateResult object at 0x00000235A1684508>1 1 使用update_many() 方法可以将所有符合条件的数据都更新: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'age': {'$gt': 18}}result = collection.update_many(condition, {'$set': {'age': 25}})print(result)print(result.matched_count, result.modified_count) 匹配所有年龄大于 18 的数据,更新条件为将这些所有满足条件的年龄都设置成 25,输出结果如下: 12<pymongo.results.UpdateResult object at 0x00000285CECC45C8>4 4 【11.11】删除数据调用 remove() 方法并指定删除的条件,此时符合条件的所有数据均会被删除 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.remove({'name': 'CCC'})print(result) 输出结果: 1{'n': 1, 'ok': 1.0} 同样的,在 PyMongo 3.x 版本里,推荐使用 delete_one() 和 delete_many() 方法 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.delete_one({'name': 'AAA'})print(result)print(result.deleted_count)result = collection.delete_many({'gender': 'female'})print(result.deleted_count) 调用 deleted_count 属性可以获取删除的数据条数,输出结果: 123<pymongo.results.DeleteResult object at 0x0000024441B245C8>11 PyMongo 官方文档:http://api.mongodb.com/python/current/api/pymongo/collection.html","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"MongoDB","slug":"MongoDB","permalink":"https://www.itrhx.com/tags/MongoDB/"}]},{"title":"Python3 爬虫学习笔记 C10","slug":"A40-Python3-spider-C10","date":"2019-09-03T15:39:27.326Z","updated":"2019-09-24T12:40:41.602Z","comments":true,"path":"2019/09/03/A40-Python3-spider-C10/","link":"","permalink":"https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/","excerpt":"Python3 爬虫学习笔记第十章 —— 【MySQL数据储存】","text":"Python3 爬虫学习笔记第十章 —— 【MySQL数据储存】 【10.1】MySQL 基本操作语句安装完 MySQL 后,打开 MySQL x.x Command Line Client - Unicode,输入密码即可登录 MySQL,也可在 MySQL 安装目录下打开 cmd 使用命令登录数据库 数据库操作1234567891011121314151617# 连接数据库mysql -u root -p# 退出数据库exit# 查看所有的数据库SHOW DATABASES;# 创建一个数据库CREATE DATABASE X;# 删除一个数据库DROP DATABASE IF EXISTS X;# 使用这个数据库USE X; 表操作12345678910111213141516171819202122232425262728293031323334353637# 查看所有的表SHOW TABLES ;# 创建一个表CREATE TABLE n(id INT, name VARCHAR(10));CREATE TABLE m(id INT, name VARCHAR(10), PRIMARY KEY (id), FOREIGN KEY (id) REFERENCES n(id), UNIQUE (name));CREATE TABLE m(id INT, name VARCHAR(10));# 直接将查询结果导入或复制到新创建的表CREATE TABLE n SELECT * FROM m;# 新创建的表与一个存在的表的数据结构类似CREATE TABLE m LIKE n;# 创建一个临时表# 临时表将在你连接MySQL期间存在。当断开连接时,MySQL将自动删除表并释放所用的空间。也可手动删除。CREATE TEMPORARY TABLE l(id INT, name VARCHAR(10));# 直接将查询结果导入或复制到新创建的临时表CREATE TEMPORARY TABLE tt SELECT * FROM n;# 删除一个存在表DROP TABLE IF EXISTS m;# 更改存在表的名称ALTER TABLE n RENAME m;RENAME TABLE n TO m;# 查看表的结构(以下五条语句效果相同)DESC n;DESCRIBE n;SHOW COLUMNS IN n;SHOW COLUMNS FROM n;EXPLAIN n;# 查看表的创建语句SHOW CREATE TABLE n; 表的结构12345678910111213141516171819202122232425# 添加字段ALTER TABLE n ADD age VARCHAR(2);# 添加字段时设定位置ALTER TABLE n ADD age VARCHAR(2) FIRST;ALTER TABLE n ADD age VARCHAR(2) AFTER name;# 修改字段在表中的位置ALTER TABLE n MODIFY age VARCHAR(2) AFTER name;# 删除字段ALTER TABLE n DROP age;# 更改字段属性和属性ALTER TABLE n CHANGE age a INT;# 只更改字段属性ALTER TABLE n MODIFY age VARCHAR(7) ;# 改变表的存储引擎ALTER TABLE t ENGINE myisam;ALTER TABLE t ENGINE innodb;# 设定自增 初始为1,只能一个字段使用,该字段为主键的一部分ALTER TABLE t AUTO_INCREMENT = 0; 表的数据123456789101112131415# 增加数据INSERT INTO n VALUES (1, 'tom', '23'), (2, 'john', '22');INSERT INTO n SELECT * FROM n; # 把数据复制一遍重新插入# 删除数据DELETE FROM n WHERE id = 2;# 更改数据UPDATE n SET name = 'tom' WHERE id = 2;# 数据查找SELECT * FROM n WHERE name LIKE '%h%';# 数据排序(反序)SELECT * FROM n ORDER BY name, id DESC ; 【10.2】Python 连接 MySQL123456789import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306)cursor = db.cursor()cursor.execute('SELECT VERSION()')data = cursor.fetchone()print('Database version:', data)cursor.execute(\"CREATE DATABASE spiders DEFAULT CHARACTER SET utf8mb4\")db.close() 通过 PyMySQL 的 connect 方法声明一个 MySQL 连接对象 db,当前 MySQL 数据库运行在本地,设定 host='localhost',用户名为 root,登录密码为 000000,运行在 3306 端口,调用 cursor() 方法获得 MySQL 的操作游标,该游标用来执行 SQL 语句,通过游标操作 execute() 方法写入 SQL 语句,第一条 SQL 语句获取 MySQL 的版本信息,调用 fetchone() 方法获得第一条数据,即 MySQL 的版本号。第二条 SQL 语句执行创建 spiders 数据库的操作,编码为 utf8mb4,运行该段代码将输出 MySQL 的版本号: 1Database version: ('8.0.17',) 【10.3】创建表1234567import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL, PRIMARY KEY (id))'cursor.execute(sql)db.close() 该段代码实现了在 spiders 数据库里创建了一个名为 students 的表,包含 id、name、age 三个字段,类型依次为 varchar、varchar、int 【10.4】插入数据1234567891011121314import pymysqlid = '17110105'user = 'TRH'age = 20db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'try: cursor.execute(sql, (id, user, age)) db.commit()except: db.rollback()db.close() commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏,使用查询语句可以看到已经插入的数据: 进阶操作:将需要插入的数据构造成一个字典,这样的做法可以让插入方法无需改动,只需要传入一个动态变化的字典就行了,改写原来的代码如下: 123456789101112131415161718192021import pymysqldata = { 'id': '17110105', 'name': 'TRH', 'age': 20}table = 'students'keys = ', '.join(data.keys())values = ', '.join(['%s']*len(data))db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)try: cursor.execute(sql, tuple(data.values())) print('数据插入成功!') db.commit()except: print('数据插入失败!') db.rollback()db.close() 传入的数是字典,将其定义为 data 变量,表名定义成变量 table,构造插入的字段 id、name 和 age。', '.join(data.keys()) 的结果就是 id, name, age,接着需要构造多个 %s 当作占位符,有三个字段,就需要构造 %s, %s, %s。首先定义长度为 1 的数组 ['%s'],然后用乘法将其扩充为 ['%s', '%s', '%s'],再调用 join() 方法,最终变成 %s, %s, %s。再利用字符串的 format() 方法将表名、字段名和占位符构造出来。最终的 SQL 语句就被动态构造成了如下语句: 1INSERT INTO students(id, name, age) VALUES (%s, %s, %s) 【10.5】更新数据1234567891011121314151617181920212223import pymysqldata = { 'id': '17110105', 'name': 'TRH', 'age': 21}table = 'students'keys = ', '.join(data.keys())values = ', '.join(['%s']*len(data))db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE'.format(table=table, keys=keys, values=values)update = ','.join([\"{key} = % s\".format(key=key) for key in data])sql += updatetry: if cursor.execute(sql, tuple(data.values())*2): print('数据插入成功!') db.commit()except: print('数据插入失败!') db.rollback()db.close() ON DUPLICATE KEY UPDATE 表示如果主键已经存在,就执行更新操作,最终被构造成如下语句: 1INSERT INTO students(id, name, age) VALUES (% s, % s, % s) ON DUPLICATE KEY UPDATE id = % s, name = % s, age = % s 【10.6】删除数据123456789101112131415import pymysqltable = 'students'condition = 'age = 20'db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'DELETE FROM {table} WHERE {condition}'.format(table=table, condition=condition)try: cursor.execute(sql) print('数据删除成功!') db.commit()except: print('数据删除失败!') db.rollback()db.close() 删除操作直接使用 DELETE 语句,指定要删除的目标表名和删除条件即可 【10.7】查询数据123456789101112131415161718import pymysqltable = 'students'db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'SELECT * FROM students WHERE age >= 20'try: cursor.execute(sql) print('Count:', cursor.rowcount) one = cursor.fetchone() print('One:', one) results = cursor.fetchall() print('Results:', results) print('Results Type:', type(results)) for row in results: print(row)except: print('查询失败!') sql = 'SELECT * FROM students WHERE age >= 20':构造一条 SQL 语句,将年龄 大于等于20 岁的学生查询出来 cursor.rowcount:调用 cursor 的 rowcount 属性获取查询结果的条数 cursor.fetchone():调用 cursor 的 fetchone() 方法,获取结果的第一条数据,返回结果是元组形式,元组的元素顺序跟字段一一对应,即第一个元素就是第一个字段 id,第二个元素就是第二个字段 name,以此类推 cursor.fetchall():调用 cursor 的 fetchall() 方法,得到结果的所有数据,它是二重元组,每个元素都是一条记录,本例中显示的是 3 条数据而不是 4 条,这是因为它的内部实现有一个偏移指针用来指向查询结果,最开始偏移指针指向第一条数据,取一次之后,指针偏移到下一条数据,这样再取的话,就会取到下一条数据了。我们最初调用了一次 fetchone 方法,这样结果的偏移指针就指向下一条数据,fetchall 方法返回的是偏移指针指向的数据一直到结束的所有数据,所以该方法获取的结果就只剩 3 个了 【10.8】实战训练 — 爬取CSDN博客标题和地址保存到 MySQL利用 requests 库构建请求,BeautifulSoup 解析库解析网页,获取自己博客文章的标题和地址,将其储存到本地 MySQL 数据库中,事先已经创建好了一个 blog 数据库,并创建了一个名为 article 的数据表,数据表包含 id、title、url 三个字段,其中 id 的 AUTO_INCREMENT 属性可以使 id 自己增加,PRIMARY KEY 关键字用于将 id 定义为主键 创建 article 数据表: 1234567import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')cursor = db.cursor()sql = 'CREATE TABLE IF NOT EXISTS article (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, PRIMARY KEY (id))'cursor.execute(sql)db.close() 获取文章标题和对应的 URL 并将其储存到 MySQL 中: 123456789101112131415161718192021222324import requestsimport pymysqlfrom bs4 import BeautifulSoupdb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')cursor = db.cursor()headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}url = \"https://blog.csdn.net/qq_36759224\"request = requests.get(url, headers=headers)soup = BeautifulSoup(request.text, 'lxml')title_list = soup.find_all('h4')for list in title_list: s = list.a.text.strip() title = s.replace('原', '') url = list.a['href'].strip() # print(title + url) cursor.execute('INSERT INTO article (title, url) VALUES (%s, %s)', (title, url))db.commit()print('数据写入完毕!')db.close() 在命令行中使用 SELECT * FROM article; 命令可以查看到数据已经成功获取并储存到了数据库中:","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"MySQL","slug":"MySQL","permalink":"https://www.itrhx.com/tags/MySQL/"}]},{"title":"Python3 爬虫学习笔记 C09","slug":"A39-Python3-spider-C09","date":"2019-08-27T10:58:37.295Z","updated":"2019-10-09T10:40:08.565Z","comments":true,"path":"2019/08/27/A39-Python3-spider-C09/","link":"","permalink":"https://www.itrhx.com/2019/08/27/A39-Python3-spider-C09/","excerpt":"Python3 爬虫学习笔记第九章 —— 【文件储存】","text":"Python3 爬虫学习笔记第九章 —— 【文件储存】 用解析器解析出数据之后,还需要对数据进行保存。保存的形式多种多样,最简单的形式是直接保存为文本文件,如 TXT、JSON、CSV 等。 【9.1】TXT 文本存储TXT 文本存储的优点:操作非常简单,TXT 文本几乎兼容任何平台;缺点:不利于检索。 【9.1.1】基本示例1234567891011121314import requestsfrom lxml import etreeheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}url = \"https://blog.csdn.net/qq_36759224\"request = requests.get(url, headers=headers)tree = etree.HTML(request.text)title_list = tree.xpath('//h4/a/text()')for title in title_list: with open('blog.txt', 'a', encoding='utf8') as fp: fp.write(title) 代码实现了我的 CSDN 博客首页所有博文标题的爬取,利用 requests 请求库发送请求,获取响应,用 XPath 获取每一篇博文的标题,然后写入 blog.txt 文件中: 123456789101112131415161718192021帝都的凛冬 最新屏蔽 CSDN 广告方法,专注阅读学习! 使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】 Python3 爬虫学习笔记 C08【解析库 Beautiful Soup】 Python3 爬虫学习笔记 C07 【解析库 lxml】 Python3 爬虫学习笔记 C06 【正则表达式】 Python3 爬虫学习笔记 C05 【Selenium + 无界面浏览器】 Python3 已经安装相关库,Pycharm 仍然报错 ModuleNotFoundError: No module named 'xxxxxx' 的解决办法 Windows/Android/iOS 等常见 User-Agent 大全 Selenium 显式等待条件及其含义 Python3 爬虫学习笔记 C04 【自动化测试工具 Selenium】 Python3 爬虫学习笔记 C03 【Ajax 数据爬取】 Python3 爬虫学习笔记 C02 【基本库 requests 的使用】 Python3 爬虫学习笔记 C01 【基本库 urllib 的使用】 利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS 光学字符识别 Tesseract-OCR 的下载、安装和基本用法 Github+jsDelivr+PicGo 打造稳定快速、高效免费图床 利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持 Python 中 if __name__ == '__main__': 的理解 Hexo 博客本地预览报错:Error: listen EADDRINUSE 0.0.0.0:4000 谷歌浏览器检查更新时出错:无法启动更新检查(错误代码为 3: 0x80080005 -- system level) 【9.1.2】打开方式open() 方法的第二个参数为打开方式,不同的打开方式如下: 读写方式 可否读写 若文件不存在 写入方式 r 读取 报错 不可写入 rb 以二进制方式读取 报错 不可写入 r+ 读取 + 写入 报错 覆盖写入 rb+ 以二进制方式读取+写入 报错 覆盖写入 w 写入 创建 覆盖写入 wb 以二进制方式写入 创建 覆盖写入 w+ 读取 + 写入 创建 覆盖写入 wb+ 以二进制方式读取+写入 创建 覆盖写入 a 写入 创建 附加写入 ab 以二进制方式写入 创建 附加写入 a+ 读取 + 写入 创建 附加写入 ab+ 以二进制方式读取+写入 创建 附加写入 【9.2】JSON 文件存储JSON,全称为 JavaScript Object Notation, 即 JavaScript 对象标记,它通过对象和数组的组合来表示数据,构造简洁但是结构化程度非常高,是一种轻量级的数据交换格式。 【9.2.1】对象和数组在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等,但是对象和数组是比较特殊且常用的两种类型 对象:它在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2, …} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。 数组:数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 [“java”, “javascript”, “vb”, …] 的索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引用得多。同样,值的类型可以是任意类型。 示例:一个 JSON 对象 123456789[{ \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}, { \"name\": \"XXX\", \"gender\": \"female\", \"birthday\": \"1999-10-18\"}] 【9.2.2】读取 JSONPython 里面的 JSON 库可以实现对 JSON 文件的读写操作,调用 JSON 库的 loads 方法将 JSON 文本字符串转为 JSON 对象、 dumps() 方法将 JSON 对象转为文本字符串 12345678910111213141516171819import jsonstr = '''[{ \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}, { \"name\": \"XXX\", \"gender\": \"female\", \"birthday\": \"1999-10-18\"}]'''data = json.loads(str)print(data)print(data[0]['name'])print(data[0].get('name'))print(data[0].get('age'))print(data[0].get('age', 25)) 使用 loads 方法将字符串转为 JSON 对象,通过索引来获取对应的内容,获取键值时有两种方式,一种是中括号加键名,另一种是通过 get 方法传入键名。使用 get 方法,如果键名不存在,则不会报错,会返回 None,get 方法还可以传入第二个参数(即默认值),尝试获取一个原字典中不存在的键名,此时默认会返回 None。如果传入第二个参数(即默认值),那么在不存在的情况下返回该默认值。 12345[{'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}, {'name': 'XXX', 'gender': 'female', 'birthday': '1999-10-18'}]TRHTRHNone25 【9.2.3】写入 JSON 文件调用 dumps 方法可以将 JSON 对象转化为字符串,然后再调用文件的 write 方法即可写入文本 123456789import jsondata = [{ 'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}]with open('data.json', 'w') as fp: fp.write(json.dumps(data)) data.json 文件: 1[{\"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}] 添加参数 indent(代表缩进字符个数),将会格式化输出:123456789import jsondata = [{ 'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}]with open('data.json', 'w') as file: file.write(json.dumps(data, indent=2)) 输出结果: 1234567[ { \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\" }] 如果 JSON 中包含中文字符,需要指定参数 ensure_ascii 为 False,另外还要规定文件输出的编码: 123456789import jsondata = [{ 'name': '小明', 'gender': '男', 'birthday': '1999年01月25日'}]with open('data.json', 'w', encoding='utf-8') as file: file.write(json.dumps(data, indent=2, ensure_ascii=False)) 输出结果: 1234567[ { \"name\": \"小明\", \"gender\": \"男\", \"birthday\": \"1999年01月25日\" }] 【9.3】CSV 文本存储CSV(Comma-Separated Values)是逗号分隔值或字符分隔值的文件格式,其文件以纯文本的形式储存表格数据(数字和文本),CSV 文件的行与行之间用换行符分隔,列与列之间用逗号分隔 【9.3.1】写入12345678import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile) writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 打开 data.csv 文件,调用 CSV 库的 writer 方法初始化写入对象,然后调用 writerow 方法传入每行的数据即可完成写入,用 Excel 打开 data.csv 文件将是表格形式 1234567id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 默认每一行之间是有一行空格的,可以使用参数 newline 来去除空行: 12345678import csvwith open('data.csv', 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 输出结果: 1234id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 列与列之间的分隔符是可以修改的,只需要传入 delimiter 参数即可: 12345678import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile, delimiter=' ') writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 输出结果: 1234567id name age10001 TRHX 2010002 Bob 2210003 Jordan 21 调用 writerows 方法也可以同时写入多行,此时参数就需要为二维列表: 123456import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile, delimiter=' ') writer.writerow(['id', 'name', 'age']) writer.writerows([['10001', 'TRHX', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]]) 输出结果仍与原来的一样 此外 CSV 库中也提供了字典的写入方式: 123456789import csvwith open('data.csv', 'w') as csvfile: fieldnames = ['id', 'name', 'age'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() writer.writerow({'id': '10001', 'name': 'TRHX', 'age': 20}) writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22}) writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21}) 首先定义 3 个字段,用 fieldnames 表示,然后将其传给 DictWriter 来初始化一个字典写入对象,接着可以调用 writeheader 方法先写入头信息,然后再调用 writerow 方法传入相应字典即可 1234567id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 【9.3.2】读取有写入方法,同样也可以使用 csv 库来读取 CSV 文件: 123456import csvwith open('data.csv', 'r', encoding='utf-8') as csvfile: reader = csv.reader(csvfile) for row in reader: print(row) 构造 Reader 对象,遍历输出每行的内容,每一行都是一个列表形式。(如果 CSV 文件中包含中文的话,还需要指定文件编码)读取结果: 1234['id', 'name', 'age']['10001', 'TRHX', '20']['10002', 'Bob', '22']['10003', 'Jordan', '21'] 此外,还可以利用 pandas 的 read_csv 方法将数据从 CSV 中读取出来(pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具) 1234import pandas as pddf = pd.read_csv('data.csv')print(df) 读取结果: 1234 id name age0 10001 TRHX 201 10002 Bob 222 10003 Jordan 21","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"文件储存","slug":"文件储存","permalink":"https://www.itrhx.com/tags/文件储存/"}]},{"title":"Python3 爬虫学习笔记 C08","slug":"A38-Python3-spider-C08","date":"2019-08-26T13:57:58.870Z","updated":"2019-09-24T12:40:34.686Z","comments":true,"path":"2019/08/26/A38-Python3-spider-C08/","link":"","permalink":"https://www.itrhx.com/2019/08/26/A38-Python3-spider-C08/","excerpt":"Python3 爬虫学习笔记第八章 —— 【解析库 Beautiful Soup】","text":"Python3 爬虫学习笔记第八章 —— 【解析库 Beautiful Soup】 【8.1】关于 Beautiful SoupBeautiful Soup 可以从 HTML 或者 XML 文件中提取数据,Beautiful Soup 可以提供一些简单的、Python 式的函数用来处理导航、搜索、修改分析树等,它借助网页的结构和属性等特性来解析网页,lxml 只会局部遍历,而 Beautiful Soup 是基于 HTML DOM 的,会载入整个文档,解析整个 DOM 树,因此时间和内存开销都会大很多,所以性能要低于lxml 抓取工具 速度 使用难度 安装难度 正则 最快 困难 无(内置) lxml 快 简单 一般 BeautifulSoup 慢 最简单 简单 【8.2】Beautiful Soup 的基本使用需要使用命令 pip install bs4 安装库,Beautiful Soup 在解析时依赖解析器,除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器: 解析器 使用方法 优势 劣势 Python 标准库 BeautifulSoup(markup, “html.parser”) Python 的内置标准库、执行速度适中 、文档容错能力强 Python 2.7.3 or 3.2.2) 前的版本中文容错能力差 LXML HTML 解析器 BeautifulSoup(markup, “lxml”) 速度快、文档容错能力强 需要安装 C 语言库 LXML XML 解析器 BeautifulSoup(markup, “xml”) 速度快、唯一支持 XML 的解析器 需要安装 C 语言库 html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档 速度慢、不依赖外部扩展 基本使用:1234from bs4 import BeautifulSoupsoup = BeautifulSoup('<p>Hello</p>', 'lxml')# soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.p.string) 输出结果:1Hello 【8.3】节点选择器直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本 【8.3.1】元素选择新建 soup.html 文件:123456789101112131415161718192021222324252627282930313233343536<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>测试bs4</title></head><body><div> 甄姬 <p>百里守约</p> <p>李白</p> 太乙真人</div><div class=\"song\"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href=\"http://www.song.com/\" title=\"赵匡义\" target=\"_self\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a> <img src=\"http://www.baidu.com/meinv.jpg\" alt=\"\"> <a href=\"\" class=\"du\">总为浮云能蔽日,长安不见使人愁</a></div><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" alt=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><b>唐朝</b></li> <li><i>宋朝</i></li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 1234567from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.title)print(type(soup.title))print(soup.title.string)print(soup.head)print(soup.p) 依次查找 title、head、p 节点。输出结果:12345678<title>测试bs4</title><class 'bs4.element.Tag'>测试bs4<head><meta charset=\"utf-8\"/><title>测试bs4</title></head><p>百里守约</p> 【8.3.2】提取信息 string 属性:获取节点包含的文本值(如果标签里面还有标签,那么string获取到的结果为None) text 属性:获取节点包含的文本值 get_text() 属性:获取节点包含的文本值 name 属性:获取节点的名称 attrs :获取所有属性 attrs[‘属性名’] :获取指定属性 依然以 soup.html 为例:1234567891011121314from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.title)print(soup.title.text)print(soup.title.get_text())print(soup.title.string)print(soup.div.string)print(soup.div.text)print(soup.title.name)print(soup.a['href']) # 获取href属性print(soup.a['title']) # 获取title属性print(soup.a['target']) # 获取target属性print(soup.a.attrs) # 获取所有属性print(soup.a.attrs['href']) # 获取href属性 输出结果:1234567891011121314151617<title>测试bs4</title>测试bs4测试bs4测试bs4None 甄姬 百里守约李白 太乙真人titlehttp://www.song.com/赵匡义_self{'href': 'http://www.song.com/', 'title': '赵匡义', 'target': '_self'}http://www.song.com/ 【8.3.3】嵌套选择12345678910from bs4 import BeautifulSouphtml = \"\"\"<html><head><title>This is a demo</title></head><body>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.head.title)print(type(soup.head.title))print(soup.head.title.string) 获取 head 节点里面的 title 节点,输出结果:123<title>This is a demo</title><class 'bs4.element.Tag'>This is a demo 【8.3.4】关联选择 contents 属性:获取某个节点元素的直接子节点 children 属性:遍历某个节点元素的子节点 descendants 属性:获取某个节点元素所有的子孙节点 parent 属性:获取某个节点元素的父节点 parents 属性:获取某个节点元素所有的祖先节点 next_sibling 属性:获取节点的下一个兄弟元素 previous_sibling 属性:获取节点的上一个兄弟元素 next_siblings 属性:获取某个节点所有后面的兄弟元素 previous_siblings 属性:获取某个节点所有前面的兄弟元素 contents 属性应用示例 12345678910111213141516171819202122from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.contents) 获取 p 节点元素的直接子节点,输出结果:123['\\n Once upon a time there were three little sisters; and their names were\\n ', <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>, '\\n', <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>, ' \\n and\\n ', <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>, '\\n and they lived at the bottom of a well.\\n '] children 属性应用示例: 123456789101112131415161718192021222324from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.children)for i, child in enumerate(soup.p.children): print(i, child) 遍历 p 节点元素的子节点,输出结果:12345678910111213141516<list_iterator object at 0x00000228E3C205F8>0 Once upon a time there were three little sisters; and their names were 1 <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>2 3 <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>4 and 5 <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>6 and they lived at the bottom of a well. descendants 属性应用示例:123456789101112131415161718192021222324from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.descendants)for i, child in enumerate(soup.p.descendants): print(i, child)获取 p 节点元素所有的子孙节点,输出结果:123456789101112131415161718192021222324<generator object descendants at 0x0000018404A4C3B8>0 Once upon a time there were three little sisters; and their names were 1 <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>2 3 <span>Elsie</span>4 Elsie5 6 7 <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>8 Lacie9 and 10 <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>11 Tillie12 and they lived at the bottom of a well.parent 属性应用示例:123456789101112131415161718from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> </p> </body></html>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.a.parent)获取 a 节点元素的父节点,输出结果:123456<p class=\"story\"> Once upon a time there were three little sisters; and their names were <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p>parents 属性应用示例:1234567891011121314from bs4 import BeautifulSouphtml = \"\"\"<html> <body> <p class=\"story\"> <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> </p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(type(soup.a.parents))print(list(enumerate(soup.a.parents)))获取 a 节点元素所有的祖先节点,输出结果:1234567891011121314151617181920212223242526<class 'generator'>[(0, <p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p>), (1, <body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body>), (2, <html><body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body></html>), (3, <html><body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body></html>)]next_sibling、previous_sibling、next_siblings、previous_siblings 属性应用示例: 1234567891011121314151617181920212223html = \"\"\"<html> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> Hello <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> </body></html>\"\"\"from bs4 import BeautifulSoupsoup = BeautifulSoup(html, 'lxml')print('Next Sibling', soup.a.next_sibling)print('Prev Sibling', soup.a.previous_sibling)print('Next Siblings', list(enumerate(soup.a.next_siblings)))print('Prev Siblings', list(enumerate(soup.a.previous_siblings))) next_sibling 和 previous_sibling 分别获取 a 节点的下一个和上一个兄弟元素,next_siblings 和 previous_siblings 则分别返回 a 节点后面和前面的兄弟节点,输出结果: 12345678Next Sibling Hello Prev Sibling Once upon a time there were three little sisters; and their names were Next Siblings [(0, '\\n Hello\\n '), (1, <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>), (2, ' \\n and\\n '), (3, <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>), (4, '\\n and they lived at the bottom of a well.\\n ')]Prev Siblings [(0, '\\n Once upon a time there were three little sisters; and their names were\\n ')] 【8.4】方法选择器节点选择器直接调用节点的名称就可以选择节点元素,如果进行比较复杂的选择的话,方法选择器是一个不错的选择,它更灵活,常见的方法有 find_all、find 等,调用它们,直接传入相应的参数,就可以灵活查询了。 【8.4.1】find_all() 方法find_all 方法可以查询所有符合条件的元素,给它传入一些属性或文本来得到符合条件的元素。find_all 方法的 API:find_all(name , attrs , recursive , text , **kwargs)新建 soup.html:123456789101112131415161718192021222324252627282930313233343536<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>测试bs4</title></head><body><div> 甄姬 <p>百里守约</p> <p>李白</p> 太乙真人</div><div class=\"song\"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href=\"http://www.song.com/\" title=\"赵匡义\" target=\"_self\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a> <img src=\"http://www.baidu.com/meinv.jpg\" alt=\"\"> <a href=\"\" class=\"du\">总为浮云能蔽日,长安不见使人愁</a></div><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><b>唐朝</b></li> <li><i>宋朝</i></li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 示例代码:12345678910from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.find_all('a'), '\\n')print(soup.find_all('a')[1], '\\n')print(soup.find_all('a')[1].text, '\\n')print(soup.find_all(['a', 'b', 'i']), '\\n')print(soup.find_all('a', limit=2), '\\n')print(soup.find_all(title='qing'), '\\n')print(soup.find_all(attrs={'id': 'feng'}), '\\n') 输出结果:12345678910111213[<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>, <a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class=\"du\" href=\"http://www.sina.com\">杜甫</a>, <a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a> 总为浮云能蔽日,长安不见使人愁 [<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>, <a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class=\"du\" href=\"http://www.sina.com\">杜甫</a>, <b>唐朝</b>, <i>宋朝</i>, <a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] [<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>] [<a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>] [<a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] 【8.4.2】find() 方法find() 方法使用方法与 find_all() 方法相同,不同的是,find 方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 返回的是所有匹配的元素组成的列表特别的: find_parents 和 find_parent:前者返回所有祖先节点,后者返回直接父节点。 find_next_siblings 和 find_next_sibling:前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。 find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。 find_all_next 和 find_next:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。 find_all_previous 和 find_previous:前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点。 【8.5】CSS 选择器使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可新建 soup.html 文件:1234567891011121314<!DOCTYPE html><html lang=\"en\"><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 通过 CSS 选择器依次选择 class=”tang” 的 div 节点下的 a 节点、id 为 feng 的节点以及其 href 元素:1234567from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.select('li'), '\\n')print(soup.select('.tang > ul > li > a')[2], '\\n')print(soup.select('#feng')[0].text, '\\n')print(soup.select('#feng')[0]['href'], '\\n') 输出结果:1234567[<li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>, <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>, <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>, <li><a class=\"du\" href=\"http://www.sina.com\">杜甫</a> </li>, <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>] <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> 凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。 http://www.haha.com 附表:CSS 选择器,来源:https://www.w3school.com.cn/cssref/css_selectors.asp 选择器 例子 例子描述 CSS .class .intro 选择 class=”intro” 的所有元素 1 #id #firstname 选择 id=”firstname” 的所有元素 1 * * 选择所有元素 2 element p 选择所有 元素 1 element,element div,p 选择所有 元素和所有 元素 1 element element div p 选择 元素内部的所有 元素 1 element>element div>p 选择父元素为 元素的所有 元素 2 element+element div+p 选择紧接在 元素之后的所有 元素 2 [attribute] [target] 选择带有 target 属性所有元素 2 [attribute=value] [target=_blank] 选择 target=”_blank” 的所有元素 2 [attribute~=value] [title~=flower] 选择 title 属性包含单词 “flower” 的所有元素 2 [attribute =value] [lang =en] 选择 lang 属性值以 “en” 开头的所有元素 2 :link a:link 选择所有未被访问的链接 1 :visited a:visited 选择所有已被访问的链接 1 :active a:active 选择活动链接 1 :hover a:hover 选择鼠标指针位于其上的链接 1 :focus input:focus 选择获得焦点的 input 元素 2 :first-letter p:first-letter 选择每个 元素的首字母 1 :first-line p:first-line 选择每个 元素的首行 1 :first-child p:first-child 选择属于父元素的第一个子元素的每个 元素 2 :before p:before 在每个 元素的内容之前插入内容 2 :after p:after 在每个 元素的内容之后插入内容 2 :lang(language) p:lang(it) 选择带有以 “it” 开头的 lang 属性值的每个 元素 2 element1~element2 p~ul 选择前面有 元素的每个 元素 3 [attribute^=value] a[src^=”https”] 选择其 src 属性值以 “https” 开头的每个 元素 3 [attribute$=value] a[src$=”.pdf”] 选择其 src 属性以 “.pdf” 结尾的所有 元素 3 [attribute*=value] a[src*=”abc”] 选择其 src 属性中包含 “abc” 子串的每个 元素 3 :first-of-type p:first-of-type 选择属于其父元素的首个 元素的每个 元素 3 :last-of-type p:last-of-type 选择属于其父元素的最后 元素的每个 元素 3 :only-of-type p:only-of-type 选择属于其父元素唯一的 元素的每个 元素 3 :only-child p:only-child 选择属于其父元素的唯一子元素的每个 元素 3 :nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个 元素 3 :nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数 3 :nth-of-type(n) p:nth-of-type(2) 选择属于其父元素第二个 元素的每个 元素 3 :nth-last-of-type(n) p:nth-last-of-type(2) 同上,但是从最后一个子元素开始计数 3 :last-child p:last-child 选择属于其父元素最后一个子元素每个 元素 3 :root :root 选择文档的根元素 3 :empty p:empty 选择没有子元素的每个 元素(包括文本节点) 3 :target #news:target 选择当前活动的 #news 元素 3 :enabled input:enabled 选择每个启用的 元素 3 :disabled input:disabled 选择每个禁用的 元素 3 :checked input:checked 选择每个被选中的 元素 3 :not(selector) :not(p) 选择非 元素的每个元素 3 ::selection ::selection 选择被用户选取的元素部分 3 【8.6】附表:Beautiful Soup 库 soup 对象常用属性与方法 基本元素 说明 返回类型 tag soup.a bs4.element.Tag name soup.a.name str attrs soup.a.attrs dict contents 子节点 list children 遍历子节点 list_iterator descendants 遍历所有子孙节点 generator parent 返回父亲标签 bs4.element.Tag parents 上行遍历父辈标签 generator prettify() 添加/n str find_all(name,attr) soup.find_all(‘a’)/([‘a’,‘b’])/(True)/(‘p’,‘course’)/(id=‘link1’)/(string=‘python’) bs4.element.ResultSet find() soup.find(‘a’)/返回第一个a标签 bs4.element.Tag","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Beautiful Soup","slug":"Beautiful-Soup","permalink":"https://www.itrhx.com/tags/Beautiful-Soup/"}]},{"title":"Python3 爬虫学习笔记 C07","slug":"A37-Python3-spider-C07","date":"2019-08-25T11:31:18.872Z","updated":"2019-09-24T12:40:30.940Z","comments":true,"path":"2019/08/25/A37-Python3-spider-C07/","link":"","permalink":"https://www.itrhx.com/2019/08/25/A37-Python3-spider-C07/","excerpt":"Python3 爬虫学习笔记第七章 —— 【解析库 lxml】","text":"Python3 爬虫学习笔记第七章 —— 【解析库 lxml】 【7.1】关于 lxml lxml 是 Python 的一个解析库,支持 HTML 和 XML 的解析,支持 XPath 解析方式,解析效率非常高,使用前需要用命令 pip3 install lxml 安装 lxml 库 【7.2】使用 XPath XPath(XML Path Language)即 XML 路径语言, lxml 解析库使用的正是 XPath 语法,最初是用来搜寻 XML 文档的,是一门在 XML 文档中查找信息的语言,它同样适用于 HTML 文档的搜索 XPath 常用规则 表达式 描述 nodename 选取此节点的所有子节点 / 从当前节点选取直接子节点 // 从当前节点选取子孙节点 . 选取当前节点 .. 选取当前节点的父节点 @ 选取属性 * 通配符,选择所有元素节点与元素名 @* 选取所有属性 [@attrib] 选取具有给定属性的所有元素 [@attrib=’value’] 选取给定属性具有给定值的所有元素 [tag] 选取所有具有指定元素的直接子节点 [tag=’text’] 选取所有具有指定元素并且文本内容是text节点 浏览器插件 XPath Helper,在线验证 XPath,谷歌商店下载地址:https://chrome.google.com/webstore/detail/hgimnogjllphhhkhlmebbmlgjoejdpjl XPath 基本使用方法:首先使用代码 from lxml import etree导入库,然后将 HTML 文档变成一个对象,再调用对象的方法去查找指定的节点,方法有两种:tree = etree.parse() 为本地文件查找,tree = etree.HTML() 为网络文件查找,再使用语句 tree.xpath() 查找指定节点。 【7.3】查找所有节点 新建一个 xpath.html 本地文件,内容如下: 123456789101112131415161718192021222324252627282930313233 <!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>xpath测试</title></head><body><div class=\"song\"> 火药 <b>指南针</b> <b>印刷术</b> 造纸术</div><div class=\"tang\"> <ul> <li class=\"balove\">停车坐爱枫林晚,霜叶红于二月花。</li> <li id=\"hua\">商女不知亡国恨,隔江犹唱后庭花。</li> <li class=\"love\" name=\"yang\">一骑红尘妃子笑,无人知是荔枝来。</li> <li id=\"bei\">葡萄美酒夜光杯,欲饮琵琶马上催。</li> <li><a href=\"http://www.baidu.com/\">百度一下</a> </li> </ul> <ol> <li class=\"balucy\">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li> <li class=\"lily\">咋暖还寒时候,最难将息。</li> <li class=\"lilei\">三杯两盏淡酒。</li> <li>怎敌他晚来风急。</li> <li>雁过也,正伤心,却是旧时相识。</li> <li>爱情三十六计</li> <li>什么是爱情</li> </ol></div></body></html> 查找所有节点:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//*')print(result) 使用 * 代表匹配所有节点,整个 xpath.html 文件中的所有节点都会被获取到,返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等,所有节点都包含在列表中,输出结果如下:1[<Element html at 0x1a836a34508>, <Element head at 0x1a836a344c8>, <Element meta at 0x1a836a345c8>, <Element title at 0x1a836a34608>, <Element body at 0x1a836a34648>, <Element div at 0x1a836a346c8>, <Element b at 0x1a836a34708>, <Element b at 0x1a836a34748>, <Element div at 0x1a836a34788>, <Element ul at 0x1a836a34688>, <Element li at 0x1a836a347c8>, <Element li at 0x1a836a34808>, <Element li at 0x1a836a34848>, <Element li at 0x1a836a34888>, <Element li at 0x1a836a348c8>, <Element a at 0x1a836a34908>, <Element ol at 0x1a836a34948>, <Element li at 0x1a836a34988>, <Element li at 0x1a836a349c8>, <Element li at 0x1a836a34a08>, <Element li at 0x1a836a34a48>, <Element li at 0x1a836a34a88>, <Element li at 0x1a836a34ac8>, <Element li at 0x1a836a34b08>] 【7.4】查找子节点 通过 / 或 // 即可查找元素的子节点或子孙节点: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ul/li')print(result) 选择 ul 节点的所有直接 li 子节点:1[<Element li at 0x2a094d044c8>, <Element li at 0x2a094d045c8>, <Element li at 0x2a094d04608>, <Element li at 0x2a094d04648>, <Element li at 0x2a094d04688>] 【7.5】查找父节点 知道了子节点,也可以用 .. 或者 parent:: 查找其父节点 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/../@class')print(result) 12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/parent::*/@class')print(result) 先查找到 ol 节点,随后获取其父节点以及其 class 属性:1['tang'] 【7.6】属性匹配 有时候 HTML 包含多个相同名的节点,而节点的属性是不一样的,此时可以用 @ 符号进行属性过滤 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//li[@class=\"balucy\"]')print(result) xpath.html 文件中,只有一个 class 为 balucy 的节点:<li class="balucy">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li>,运行以上代码将返回一个该元素:1[<Element li at 0x16e53aa54c8>] 【7.7】文本获取 使用 text() 方法即可提取节点中的文本: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//li[@class=\"balucy\"]/text()')print(result) 输出结果:1['寻寻觅觅冷冷清清,凄凄惨惨戚戚。'] 再次观察 xpath.html 文件中的 <ol></ol>这一部分:123456789<ol> <li class=\"balucy\">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li> <li class=\"lily\">咋暖还寒时候,最难将息。</li> <li class=\"lilei\">三杯两盏淡酒。</li> <li>怎敌他晚来风急。</li> <li>雁过也,正伤心,却是旧时相识。</li> <li>爱情三十六计</li> <li>什么是爱情</li></ol> 如果我们想要提取 <li> 节点里面所有的文本,就可以使用 html.xpath('//ol/li/text()') 语句:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/li/text()')print(result) 输出结果:1['寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '咋暖还寒时候,最难将息。', '三杯两盏淡酒。', '怎敌他晚来风急。', '雁过也,正伤心,却是旧时相识。', '爱情三十六计', '什么是爱情'] 同样还有另一种方法,使用 html.xpath('//ol//text()') 语句,// 将会选取所有子孙节点的文本,<ol> 和 <li> 节点下的换行符也将被提取出来:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol//text()')print(result) 输出结果:1['\\n ', '寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '\\n ', '咋暖还寒时候,最难将息。', '\\n ', '三杯两盏淡酒。', '\\n ', '怎敌他晚来风急。', '\\n ', '雁过也,正伤心,却是旧时相识。', '\\n ', '爱情三十六计', '\\n ', '什么是爱情', '\\n '] 【7.8】属性获取 与属性匹配一样,属性获取仍然使用 @: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ul/li[5]/a/@href')print(result) 获取 href 属性: 1['http://www.baidu.com/'] 【7.9】一个属性包含多个值的匹配某个节点的某个属性可能有多个值,例如:1<li class=\"li li-first\"><a href=\"link.html\">first item</a></li> li 节点的 class 属性有 li 和 li-first 两个值,如果使用 html.xpath('//li[@class="li"] 语句,将无法成功匹配,这时就需要使用 contains 方法了,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了 1234567from lxml import etreetext = ''' <li class=\"li li-first\"><a href=\"link.html\">first item</a></li> '''html = etree.HTML(text)result = html.xpath('//li[contains(@class, \"li\")]/a/text()')print(result) 输出结果:1['first item'] 【7.10】多个属性匹配一个节点XPath 还可以根据多个属性来确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符 and 来连接:1234567from lxml import etreetext = ''' <li class=\"li\" name=\"item\"><a href=\"link.html\">first item</a></li>'''html = etree.HTML(text)result = html.xpath('//li[@class=\"li\" and @name=\"item\"]/a/text()')print(result) 输出结果:1['first item'] 示例中运用了运算符 and 来连接,此外常见的运算符如下: 运算符 描述 实例 返回值 or 或 age=19 or age=20 如果 age 是 19 或者 20,则返回 true。如果 age 是其他值,则返回 false and 与 age>19 and age<21 如果 age 大于 19 且小于 21,则返回 true。如果 age 是其他值,则返回 false mod 计算除法的余数 5 mod 2 1 | 计算两个节点集 //book | //cd 返回所有拥有 book 和 cd 元素的节点集 + 加法 10 + 5 15 - 减法 10 - 5 5 * 乘法 10 * 5 50 div 除法 10 div 5 2 = 等于 age=19 如果 age 是 19,则返回 true。如果 age 不是 19,则返回 false != 不等于 age!=19 如果 age 不是 19,则返回 true。如果 age 是 19,则返回 false < 小于 age<19 如果 age 小于 19,则返回 true。如果 age 不小于 19,则返回 false <= 小于或等于 age<=19 如果 age 小于等于 19,则返回 true。如果 age 大于 19,则返回 false > 大于 age>19 如果 age 大于 19,则返回 true。如果 age 不大于 19,则返回 false >= 大于或等于 age>=19 如果 age 大于等于 19,则返回 true。如果 age 小于 19,则返回 false 【7.11】按顺序选择节点某些属性可能同时匹配了多个节点,如果要选择其中几个节点,可以利用中括号传入索引的方法获取特定次序的节点12345678910111213141516171819202122from lxml import etreetext = '''<div> <ul> <li class=\"item-0\"><a href=\"link1.html\">first item</a></li> <li class=\"item-1\"><a href=\"link2.html\">second item</a></li> <li class=\"item-inactive\"><a href=\"link3.html\">third item</a></li> <li class=\"item-1\"><a href=\"link4.html\">fourth item</a></li> <li class=\"item-0\"><a href=\"link5.html\">fifth item</a> </ul> </div>'''html = etree.HTML(text)result = html.xpath('//li[1]/a/text()')print(result)result = html.xpath('//li[last()]/a/text()')print(result)result = html.xpath('//li[position()<3]/a/text()')print(result)result = html.xpath('//li[last()-2]/a/text()')print(result) li[1]:选取第一个 li 节点; li[last()]:选取最后一个 li 节点; position()<3:选取位置小于 3 的 li 节点; li[last()-2]:选取倒数第三个 li 节点 输出结果:1234['first item']['fifth item']['first item', 'second item']['third item'] 【7.12】节点轴选择节点轴选择:获取子元素、兄弟元素、父元素、祖先元素等12345678910111213141516171819202122232425262728from lxml import etreetext = '''<div> <ul> <li class=\"item-0\"><a href=\"link1.html\"><span>first item</span></a></li> <li class=\"item-1\"><a href=\"link2.html\">second item</a></li> <li class=\"item-inactive\"><a href=\"link3.html\">third item</a></li> <li class=\"item-1\"><a href=\"link4.html\">fourth item</a></li> <li class=\"item-0\"><a href=\"link5.html\">fifth item</a> </ul> </div>'''html = etree.HTML(text)result = html.xpath('//li[1]/ancestor::*')print(result)result = html.xpath('//li[1]/ancestor::div')print(result)result = html.xpath('//li[1]/attribute::*')print(result)result = html.xpath('//li[1]/child::a[@href=\"link1.html\"]')print(result)result = html.xpath('//li[1]/descendant::span')print(result)result = html.xpath('//li[1]/following::*[2]')print(result)result = html.xpath('//li[1]/following-sibling::*')print(result) 输出结果:1234567[<Element html at 0x1d3749e9548>, <Element body at 0x1d3749e94c8>, <Element div at 0x1d3749e9488>, <Element ul at 0x1d3749e9588>][<Element div at 0x1d3749e9488>]['item-0'][<Element a at 0x1d3749e9588>][<Element span at 0x1d3749e9488>][<Element a at 0x1d3749e9588>][<Element li at 0x1d3749e94c8>, <Element li at 0x1d3749e95c8>, <Element li at 0x1d3749e9608>, <Element li at 0x1d3749e9648>] 基本语法:轴名称::节点测试[谓语] 轴名称对应的结果: 轴名称 结果 ancestor 选取当前节点的所有先辈(父、祖父等) ancestor-or-self 选取当前节点的所有先辈(父、祖父等)以及当前节点本身 attribute 选取当前节点的所有属性 child 选取当前节点的所有子元素 descendant 选取当前节点的所有后代元素(子、孙等) descendant-or-self 选取当前节点的所有后代元素(子、孙等)以及当前节点本身 following 选取文档中当前节点的结束标签之后的所有节点 namespace 选取当前节点的所有命名空间节点 parent 选取当前节点的父节点 preceding 选取文档中当前节点的开始标签之前的所有节点 preceding-sibling 选取当前节点之前的所有同级节点 self 选取当前节点 实例: 例子 结果 child::book 选取所有属于当前节点的子元素的 book 节点 attribute::lang 选取当前节点的 lang 属性 child::* 选取当前节点的所有子元素 attribute::* 选取当前节点的所有属性 child::text() 选取当前节点的所有文本子节点 child::node() 选取当前节点的所有子节点 descendant::book 选取当前节点的所有 book 后代 ancestor::book 选择当前节点的所有 book 先辈 ancestor-or-self::book 选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点) child::*/child::price 选取当前节点的所有 price 孙节点","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"lxml","slug":"lxml","permalink":"https://www.itrhx.com/tags/lxml/"},{"name":"XPath","slug":"XPath","permalink":"https://www.itrhx.com/tags/XPath/"}]},{"title":"Python3 爬虫学习笔记 C06","slug":"A36-Python3-spider-C06","date":"2019-08-24T10:37:05.278Z","updated":"2019-09-24T12:40:18.770Z","comments":true,"path":"2019/08/24/A36-Python3-spider-C06/","link":"","permalink":"https://www.itrhx.com/2019/08/24/A36-Python3-spider-C06/","excerpt":"Python3 爬虫学习笔记第六章 —— 【正则表达式】","text":"Python3 爬虫学习笔记第六章 —— 【正则表达式】 【6.1】关于正则表达式正则表达式是对字符串操作的一种逻辑公式,用定义好的特定字符和这些特定字符的组合组成一个规则字符串,这个规则字符串原来表达对字符串的一种过滤逻辑,从而实现字符串的检索、替换、匹配验证等。Python 的 re 库提供了整个正则表达式的实现,包含五种方法:match、search、findall、sub、compile常用的匹配规则: 模式 描述 \\w 匹配字母、数字及下划线 \\W 匹配不是字母、数字及下划线的字符 \\s 匹配任意空白字符,等价于 [\\t\\n\\r\\f] \\S 匹配任意非空字符 \\d 匹配任意数字,等价于 [0-9] \\D 匹配任意非数字的字符 \\A 匹配字符串开头 \\z 匹配字符串结尾,如果存在换行,同时还会匹配换行符 \\Z 匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串 \\G 匹配最后匹配完成的位置 \\n 匹配一个换行符 \\t 匹配一个制表符 ^ 匹配一行字符串的开头 $ 匹配一行字符串的结尾 . 匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符 […] 用来表示一组字符,单独列出,比如 [amk] 匹配 a、m 或 k [^…] 不在 [] 中的字符,比如 匹配除了 a、b、c 之外的字符 * 匹配 0 个或多个表达式 + 匹配 1 个或多个表达式 ? 匹配 0 个或 1 个前面的正则表达式定义的片段,非贪婪方式 {n} 精确匹配 n 个前面的表达式 {n, m} 匹配 n 到 m 次由前面正则表达式定义的片段,贪婪方式 a\\ b 匹配 a 或 b ( ) 匹配括号内的表达式,也表示一个组 【6.2】re.match 方法match() 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None,在 match() 方法中,第一个参数传入正则表达式,第二个参数传入要匹配的字符串。12345678import recontent = 'This is a Demo_123 4567_I Love China'print(len(content))result = re.match('^This\\s\\w\\w\\s\\w\\s\\w{5}\\d{3}\\s\\w{6}', content)print(result)print(result.group())print(result.span()) 输出结果:123436<_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>This is a Demo_123 4567_I(0, 25) 打印 result 结果是 SRE_Match 对象,表明匹配成功。SRE_Match 对象有两种方法:group() 方法可以输出匹配到的内容;span() 方法可以输出匹配的范围。 【6.2.1】提取内容使用括号将想提取的子字符串括起来。括号实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应每一个分组,调用 group() 方法传入分组的索引即可获取提取的结果。12345678import recontent = 'This is a Demo_123 4567_I Love China'result = re.match('^This\\s\\w\\w\\s\\w\\s(\\w{5})\\d{3}\\s\\w{6}', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>This is a Demo_123 4567_IDemo_(0, 25) 【6.2.2】通用匹配如果每个字符都用都用一个符号来匹配的话就显得比较麻烦,可以用 .*来匹配,. 可以匹配除换行符外的任意字符,* 代表匹配前面的字符无限次。1234567import recontent = 'This is a Demo_123 4567_I Love China'result = re.match('^This.*China$', content)print(result)print(result.group())print(result.span()) 输出结果:123<_sre.SRE_Match object; span=(0, 36), match='This is a Demo_123 4567_I Love China'>This is a Demo_123 4567_I Love China(0, 36) 【6.2.3】贪婪匹配12345678import recontent = 'This is a Demo_1234567_I Love China'result = re.match('^This.*(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>This is a Demo_1234567_I Love China7(0, 35) .* 为贪婪匹配,会匹配尽可能多的字符,所以 \\d+ 只会匹配到最后一个数字,而不是所有的数字 【6.2.4】非贪婪匹配12345678import recontent = 'This is a Demo_1234567_I Love China'result = re.match('^This.*?(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>This is a Demo_1234567_I Love China1234567(0, 35) .*? 为非贪婪匹配,会匹配尽可能少的字符,所以 \\d+ 会匹配到所有的数字 【6.2.5】转义匹配当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。例如 . 可以用 \\. 来匹配:123456import recontent = '(博客)www.itrhx.com'result = re.match('\\(博客\\)www\\.itrhx\\.com', content)print(result)print(result.group()) 输出结果:12<_sre.SRE_Match object; span=(0, 17), match='(博客)www.itrhx.com'>(博客)www.itrhx.com 【6.2.6】修饰符修饰符用来解决换行、大小写等问题,较为常用的有 re.S 和 re.I。 修饰符 描述 re.S 使 . 匹配包括换行在内的所有字符 re.I 使匹配对大小写不敏感 re.L 做本地化识别(locale-aware)匹配 re.M 多行匹配,影响 ^ 和 $ re.U 根据 Unicode 字符集解析字符。这个标志影响 \\w、\\W、\\b 和 \\B re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 示例:123456789import recontent = '''This is a Demo_1234567 _I Love China'''result = re.match('^This.*?(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 示例中 content 字段进行了换行处理,如果没有修饰符,就会报错:12345Traceback (most recent call last):None File \"F:/PycharmProjects/Python3爬虫/test.py\", line 7, in <module> print(result.group())AttributeError: 'NoneType' object has no attribute 'group' 添加 re.S 修饰符后即可匹配成功:123456789import recontent = '''This is a Demo_1234567 _I Love China'''result = re.match('^This.*?(\\d+).*China$', content, re.S)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:12345<_sre.SRE_Match object; span=(0, 46), match='This is a Demo_1234567\\n _I Love China'>This is a Demo_1234567 _I Love China1234567(0, 46) 【6.3】re.search 方法match() 方法只能从字符串的开头开始匹配,一旦开头不匹配,那么整个匹配就失败了,match() 方法更适合用来检测某个字符串是否符合某个正则表达式的规则,而 search() 方法则会扫描整个字符串并返回第一个成功的匹配123456import recontent = 'This is a Demo_1234567_I Love China'result = re.search('a.*?(\\d{5})', content)print(result)print(result.group(1)) 输出结果:12<_sre.SRE_Match object; span=(8, 20), match='a Demo_12345'>12345 【6.4】re.findall 方法search() 方法则会扫描整个字符串,但是返回的是第一个成功的匹配,而 findall() 方法将会返回所有成功的匹配12345678910111213141516171819202122232425262728import rehtml = '''<div id=\"songs-list\"> <h2 class=\"title\"> 民谣 </h2> <p class=\"introduction\"> 民谣歌曲列表 </p> <ul id=\"list\" class=\"list-group\"> <li data-view=\"2\"> 七里香 </li> <li data-view=\"7\"> <a href=\"/2.mp3\" singer=\"赵雷\"> 理想 </a> </li> <li data-view=\"4\" class=\"active\"> <a href=\"/3.mp3\" singer=\"许巍\"> 像风一样自由 </a> </li> <li data-view=\"6\"><a href=\"/4.mp3\" singer=\"安与骑兵\"> 红山果 </a></li> <li data-view=\"5\"><a href=\"/5.mp3\" singer=\"薛之谦\"> 意外 </a></li> <li data-view=\"5\"> <a href=\"/6.mp3\" singer=\"马頔\"> 但南山南 </a> </li> </ul> </div>'''results = re.findall('<li.*?href=\"(.*?)\".*?singer=\"(.*?)\">(.*?)</a>', html, re.S)print(results)print(type(results))for result in results: print(result) print(result[0], result[1], result[2]) 输出结果:123456789101112[('/2.mp3', '赵雷', ' 理想 '), ('/3.mp3', '许巍', ' 像风一样自由 '), ('/4.mp3', '安与骑兵', ' 红山果 '), ('/5.mp3', '薛之谦', ' 意外 '), ('/6.mp3', '马頔', ' 但南山南 ')]<class 'list'>('/2.mp3', '赵雷', ' 理想 ')/2.mp3 赵雷 理想 ('/3.mp3', '许巍', ' 像风一样自由 ')/3.mp3 许巍 像风一样自由 ('/4.mp3', '安与骑兵', ' 红山果 ')/4.mp3 安与骑兵 红山果 ('/5.mp3', '薛之谦', ' 意外 ')/5.mp3 薛之谦 意外 ('/6.mp3', '马頔', ' 但南山南 ')/6.mp3 马頔 但南山南 【6.5】re.sub 方法与字符串的 replace() 方法类似,sub() 方法可以对文本进行修改,sub() 方法第一个参数为匹配对象,第二个参数为替换成的字符串,如果要去掉匹配对象的话,可以赋值为空,第三个参数为原来的字符串12345import recontent = '87dsf4as2w4jh1k4kdl4'result = re.sub('\\d+', '', content)print(result) 输出结果:1dsfaswjhkkdl 【6.5】re.compile() 方法compile() 方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用123456789101112import recontent1 = '北京时间:2019-08-24 18:30'content2 = '伦敦时间:2019-08-24 11:30'content3 = '巴黎时间:2019-08-24 12:30'content4 = '外星时间:9019-99-66 50:30'pattern = re.compile('\\d{2}:\\d{2}')result1 = re.sub(pattern, '', content1)result2 = re.sub(pattern, '', content2)result3 = re.sub(pattern, '', content3)result4 = re.sub(pattern, '', content4)print(result1, result2, result3, result4) 利用 compile() 方法将正则表达式编译成一个正则表达式对象,以便复用,然后用 sub() 方法去掉具体时间输出结果:1北京时间:2019-08-24 伦敦时间:2019-08-24 巴黎时间:2019-08-24 外星时间:9019-99-66","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"正则表达式","slug":"正则表达式","permalink":"https://www.itrhx.com/tags/正则表达式/"}]},{"title":"Python3 爬虫学习笔记 C05","slug":"A35-Python3-spider-C05","date":"2019-08-23T12:13:55.085Z","updated":"2019-09-24T12:40:15.920Z","comments":true,"path":"2019/08/23/A35-Python3-spider-C05/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A35-Python3-spider-C05/","excerpt":"Python3 爬虫学习笔记第五章 —— 【Selenium + 无界面浏览器】","text":"Python3 爬虫学习笔记第五章 —— 【Selenium + 无界面浏览器】 【5.1】关于无界面浏览器无界面(headless)浏览器,会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器更高效。Selenium 搭配无界面浏览器使用,被称为爬虫利器,常用的无界面浏览器有:PhantomJS、Headless Chrome、Headless Firefox,其中,18年3月,PhantomJS 的作者在 GitHub 上宣布暂停开发 PhantomJS,现在使用 PhantomJS 会出现警告:UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead,所以推荐使用谷歌或者火狐的无界面浏览器 【5.2】PhantomJS下载 PhantomJS:https://phantomjs.org/download.htmlpath 为 PhantomJS 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'driver = webdriver.PhantomJS(executable_path=path)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close() 【5.3】Headless Chrome下载 Chromedriver:http://chromedriver.storage.googleapis.com/index.html需要本地有 Chrome 浏览器,path 为 Headless Chrome 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数1234567891011from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionschrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')path = 'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'driver = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close() 【5.4】Headless Firefox下载 geckodriver:https://github.com/mozilla/geckodriver/releases/需要本地有 Firefox 浏览器,path 为 Headless Firefox 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数12345678910from selenium.webdriver import Firefoxfrom selenium.webdriver.firefox.options import Optionsoptions = Options()options.add_argument('-headless')path = 'F:\\PycharmProjects\\Python3爬虫\\geckodriver.exe'driver = Firefox(executable_path=path, firefox_options=options)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"},{"name":"无界面浏览器","slug":"无界面浏览器","permalink":"https://www.itrhx.com/tags/无界面浏览器/"}]},{"title":"常见 User-Agent 大全","slug":"A34-UserAgent","date":"2019-08-23T01:28:22.624Z","updated":"2019-09-24T12:47:21.096Z","comments":true,"path":"2019/08/23/A34-UserAgent/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A34-UserAgent/","excerpt":"","text":"User Agent 中文名为用户代理,简称 UA,是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。Python 爬虫通过伪装 UA 可以绕过某些检测。 以下为搜集的常见的各浏览器的 User-Agent,其中: 安卓操作系统:Android 7.1.1;OPPO R9sk Build/NMF26F PC操作系统:Windows 10 64位 10.0.18362.10000 其他操作系统:iOS、Backerry、WebOS、Symbian、Windows Phone 相关链接: 手机User-Agent大全:http://www.fynas.com/ua User-Agent在线检测:http://www.user-agent.cn/ 常用User-Agent大全:http://www.jsons.cn/useragent/ Windows10 Windows10 / Chrome 75.0.3770.142Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Windows10 / Firefox 69.0b15Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0 Windows10 / Opera 63.0.3368.43Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 OPR/63.0.3368.43 Windows10 / Edge 44.18362.1.0User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362 Windows10 / IE 11.10000.18362.0User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; LCTE; rv:11.0) like Gecko Windows10 x64 / Safari 5.1.4(7534.54.16)Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16 Windows10 / QQ浏览器 10.5(3739)Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3722.400 QQBrowser/10.5.3739.400 Windows10 / 360安全浏览器 10.0.1977.0Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE Windows10 / 360极速浏览器 11.0.2179.0Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE Windows10 / UC浏览器 6.2.3964.2Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36 Windows10 / 搜狗浏览器 8.5.10.31270Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0 Windows10 / 猎豹浏览器 6.5.115.19331.8001Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER Windows10 / 傲游浏览器 5.2.7.5000Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36 Windows10 / 2345加速浏览器 10.1.0.19399Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36 Android Android / Chrome 76.0.3809.111Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 Android / Firefox 68.0.2Mozilla/5.0 (Android 7.1.1; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0 Android / Opera 53.0.2569.141117Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 OPR/53.0.2569.141117 Android / Edge 42.0.2.3819Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.2.3819 Android / QQ浏览器 9.6.1.5190Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.6 Mobile Safari/537.36 Android / OPPO浏览器 10.5.1.2_2c91537Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 OppoBrowser/10.5.1.2 Android / 360浏览器 8.2.0.162Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.97 Mobile Safari/537.36 Android / 360极速浏览器 1.0.100.1078Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 360 Alitephone Browser (1.5.0.90/1.0.100.1078) mso_sdk(1.0.0) Android / UC浏览器 12.6.0.1040Mozilla/5.0 (Linux; U; Android 7.1.1; zh-CN; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.6.0.1040 Mobile Safari/537.36 Android / 猎豹浏览器 5.12.3Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 LieBaoFast/5.12.3 Android / 百度浏览器 7.19Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.116 Mobile Safari/537.36 T7/9.1 baidubrowser/7.19.13.0 (Baidu; P1 7.1.1) Android / 搜狗浏览器 5.22.8.71677Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.106 Mobile Safari/537.36 AWP/2.0 SogouMSE,SogouMobileBrowser/5.22.8 Android / 2345浏览器 11.0.1Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Mb2345Browser/11.0.1 其他 iPhone3Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/1A542a Safari/419.3 iPhone4Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7 iPhone6sMozilla/5.0 (iPhone 6s; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0 MQQBrowser/8.3.0 Mobile/15B87 Safari/604.1 MttCustomUA/2 QBWebViewType/1 WKType/1 iPadMozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10 iPodMozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5 BlackBerryMozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+ WebOS HP TouchpadMozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0 Nokia N97Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124 Windows Phone MangoMozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"User-Agent","slug":"User-Agent","permalink":"https://www.itrhx.com/tags/User-Agent/"}]},{"title":"Selenium 显式等待条件及其含义","slug":"A33-selenium","date":"2019-08-23T01:28:22.478Z","updated":"2019-09-24T12:47:18.475Z","comments":true,"path":"2019/08/23/A33-selenium/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A33-selenium/","excerpt":"","text":"等待条件 含义 title_is 标题是某内容 title_contains 标题包含某内容 presence_of_element_located 节点加载出,传入定位元组,如 (By.ID, ‘p’) visibility_of_element_located 节点可见,传入定位元组 visibility_of 可见,传入节点对象 presence_of_all_elements_located 所有节点加载出 text_to_be_present_in_element 某个节点文本包含某文字 text_to_be_present_in_element_value 某个节点值包含某文字 frame_to_be_available_and_switch_to_it frame 加载并切换 invisibility_of_element_located 节点不可见 element_to_be_clickable 节点可点击 staleness_of 判断一个节点是否仍在 DOM,可判断页面是否已经刷新 element_to_be_selected 节点可选择,传节点对象 element_located_to_be_selected 节点可选择,传入定位元组 element_selection_state_to_be 传入节点对象以及状态,相等返回 True,否则返回 False element_located_selection_state_to_be 传入定位元组以及状态,相等返回 True,否则返回 False alert_is_present 是否出现 Alert 更多等待条件极其用法介绍:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions Selenium 的使用:https://www.itrhx.com/2019/08/22/A32-Python3-spider-C04/","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"}]},{"title":"Python3 爬虫学习笔记 C04","slug":"A32-Python3-spider-C04","date":"2019-08-23T01:28:22.327Z","updated":"2019-09-24T12:43:57.196Z","comments":true,"path":"2019/08/23/A32-Python3-spider-C04/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A32-Python3-spider-C04/","excerpt":"Python3 爬虫学习笔记第四章 —— 【自动化测试工具 Selenium】","text":"Python3 爬虫学习笔记第四章 —— 【自动化测试工具 Selenium】 Selenium 是一个用于 Web 应用程序测试的工具。Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些 JavaScript 动态渲染的页面来说,此种抓取方式非常有效。本文重点以 Selenium 使用谷歌浏览器的 Webdriver 为例。 【4.1】下载驱动使用 Selenium 操作不同浏览器,需要不同浏览器相应的驱动支持: 浏览器 驱动名称 下载地址 备注 谷歌浏览器 chromedriver 点击进入下载页面 需要根据自己浏览器的版本下载不同版本的驱动 火狐浏览器 geckodriver 点击进入下载页面 需要根据自己的操作系统下载对应的驱动 IE IEDriverServer 点击进入下载页面 根据自己 selenium 版本和系统版本下载对应版本的驱动, selenium 版本可以在cmd中输入pip show selenium查看 【4.2】声明浏览器对象不同浏览器的对象声明方法:1234567from selenium import webdriverbrowser = webdriver.Chrome() # 谷歌浏览器browser = webdriver.Firefox() # 火狐浏览器browser = webdriver.Edge() # Edgebrowser = webdriver.PhantomJS() # PhantomJS无界面浏览器browser = webdriver.Safari() # Safari浏览器 【4.3】访问页面1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')print(browser.page_source)browser.close() 运行代码就会自动打开谷歌浏览器,实现了用 get() 方法访问 www.itrhx.com ,path 里面的内容是谷歌浏览器驱动的目录, r 表示不转义,使用真实字符。print(browser.page_source) 表示打印页面源代码 【4.4】启动参数Chrome Options 是一个 Chrome 的参数对象,在此对象中使用 add_argument() 方法可以添加启动参数,添加完毕后可以在初始化 Webdriver 对象时将此 Options 对象传入,则可以实现以特定参数启动Chrome。示例:123456789101112from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'# 实例化一个启动参数对象chrome_options = Options()# 添加启动参数chrome_options.add_argument('--window-size=1366,768')# 将参数对象传入Chrome,则启动了一个设置了窗口大小的Chromebrowser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://www.itrhx.com') 这样就启动了一个1366x768分辨率的浏览器常见的启动参数: 启动参数 作用 –user-agent=”” 设置请求头的 User-Agent –window-size=xxx, xxx 设置浏览器分辨率 –headless 无界面运行 –start-maximized 最大化运行 –incognito 隐身模式 –disable-javascript 禁用javascript –disable-infobars 禁用“浏览器正在被自动化程序控制”的提示 所有的启动参数:https://peter.sh/experiments/chromium-command-line-switches/ 【4.5】查找节点Selenium 可以驱动浏览器完成各种操作,比如填充表单、模拟点击等。要完成这些操作,实现要知道在哪里点击,哪里填充,这就是 Selenium 节点查找 【4.5.1】查找单个节点所有获取单个节点的方法: find_element_by_id 【通过元素的 id 来选择】例:<div id='bdy-inner'>test</div>,查找:driver.find_element_by_id('bdy-inner') find_element_by_name 【通过元素的 name 来选择】例:<input name="username" type="text" />,查找:driver.find_element_by_name('password') find_element_by_xpath 【通过 xpath 选择】例:<form id="loginForm">,查找:driver.find_element_by_xpath("//form[@id='loginForm']") find_element_by_link_text 【通过链接地址选择】例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('continue') find_element_by_partial_link_text 【通过链接的部分地址选择】例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('cont') find_element_by_tag_name 【通过元素的名称选择】例:<h1>welcome<h1>,查询:driver.find_element_by_tag_name('h1') find_element_by_class_name 【通过元素的 class 选择】例:<p class="content">welcome to TRHX'S BLOG!</p>,查询:driver.find_element_by_class_name('content') find_element_by_css_selector 【通过元素的 class 选择】例:<div class='bdy-inner'>test</div>,查询:driver.find_element_by_css_selector('div.bdy-inner') find_element() 【通用方法,需要传递两个参数:查找方式 By 和值】例:driver.find_element_by_id('inner') 等价于 find_element(By.ID, inner),使用时需要from selenium.webdriver.common.by import By 示例:12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')blog_title = browser.find_elements_by_class_name(('title'))print(blog_title[0].text)browser.close() 输出结果:1TRHX'S BLOG 【4.5.2】查找多个节点所有获取多个节点的方法:(与查找单个节点的区别是 element 多加了个 s) find_elements_by_id find_elements_by_name find_elements_by_xpath find_elements_by_link_text find_elements_by_partial_link_text find_elements_by_tag_name find_elements_by_class_name find_elements_by_css_selector find_elements() 示例:123456789from selenium import webdriverfrom selenium.webdriver.common.by import Bypath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')article_title = browser.find_elements(By.XPATH, \"//h2[@class='title']\")print(article_title)browser.close() 【4.6】节点交互Selenium 可以驱动浏览器来执行一些操作,也就是说可以让浏览器模拟执行一些动作。称为节点交互,比较常见的用法有: send_keys:模拟按键输入 clear:清除元素的内容 click:单击元素 submit:提交表单 示例:123456789from selenium import webdriverfrom selenium.webdriver.common.keys import Keyspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')search = browser.find_element_by_xpath('//div[@class=\"cover-wrapper\"]/cover/div/form/input')search.send_keys(\"Python\")search.send_keys(Keys.ENTER) 此处模拟了键盘,需要导入键盘类 Keys(),send_keys(Keys.ENTER)表示模拟回车键,程序首先打开 www.itrhx.com ,也就是我的博客,然后通过 xpath 找到搜索框,输入 Python 并回车,等待结果显示出来更多节点交互动作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement 【4.7】动作链Selenium 还有另外一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,那就是动作链。以一个拖曳实例为例:http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable12345678910111213from selenium import webdriverfrom selenium.webdriver import ActionChainspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'browser.get(url)browser.switch_to.frame('iframeResult')source = browser.find_element_by_css_selector('#draggable')target = browser.find_element_by_css_selector('#droppable')actions = ActionChains(browser)actions.drag_and_drop(source, target)actions.perform() 依次选中要拖曳的节点和拖曳到的目标节点,接着声明 ActionChains 对象并将其赋值为 actions 变量,然后通过调用 actions 变量的 drag_and_drop() 方法,再调用 perform() 方法执行动作,此时就完成了拖曳操作,更多动作链操作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains 【4.8】执行 JavaScriptSelenium API 并没有提供执行 JavaScript 的方法,但是实际上是可以实现的。比如,下拉进度条,它可以直接模拟运行 JavaScript,此时使用 execute_script() 方法即可实现示例:1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')browser.execute_script('alert(\"已到达最底端!\")') 以上代码实现了利用 execute_script() 方法将进度条下拉到最底部,然后弹出 alert 提示框。 【4.9】禁用加载使用Selenium 时,限制图片和 Javascript 执行,从而提高网页加载速度。123456789101112131415from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'options = webdriver.ChromeOptions()prefs = { 'profile.default_content_setting_values': { 'images': 2, 'notifications' : 2, # 禁用弹窗 'javascript': 2 # 2即为禁用的意思 }}options.add_experimental_option('prefs', prefs)browser = webdriver.Chrome(executable_path=path, chrome_options=options)browser.get('http://www.itrhx.com') 【4.10】获取节点信息通过 page_source 属性可以获取网页的源代码,然后可以使用解析库(如正则表达式、Beautiful Soup等)来提取相关信息,Selenium 已经提供了选择节点的方法,返回的是 WebElement 类型,它也有相关的方法和属性来直接提取节点信息,如属性、文本等。就不需要再次使用解析库来提取信息了 【4.10.1】获取属性使用 get_attribute() 方法来获取节点的属性:123456789from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)meta = browser.find_element_by_id('header-meta')print(meta)print(meta.get_attribute('class')) 输出结果:12<selenium.webdriver.remote.webelement.WebElement (session=\"d03cdaa497441d2e2a5161139b4a7ea5\", element=\"83f8fff9-60d7-4e9a-ade3-a8e97c9f0844\")>meta 【4.10.2】获取文本值每个 WebElement 节点都有 text 属性,直接调用这个属性就可以得到节点内部的文本信息,相当于 Beautiful Soup 的 get_text() 方法、pyquery 的 text() 方法示例:12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)footer_info = browser.find_element_by_id('footer')print(footer_info.text) 输出结果:123Copyright 2018-2019 TRHX'BLOG | 鄂ICP备19003281号-4 | 本站已勉强存活了 376 天 20 小时 57 分 52 秒 | 站点地图 | 站长统计PoweredHexo HostedGitHub DNRAliyun CDNjsDelivr ThemeMaterial X BY-NC-SA 4.0 Link996.ICU UV4898 PV22066 WordCount54.9k 【4.10.3】获取 ID、位置、标签名、大小其他属性,比如 id 属性可以获取节点 id,location 属性可以获取该节点在页面中的相对位置,tag_name 属性可以获取标签名称,size 属性可以获取节点的大小等示例:1234567891011from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)readmore = browser.find_element_by_class_name('readmore')print(readmore.id)print(readmore.location)print(readmore.tag_name)print(readmore.size) 输出结果:12347df561d3-7ea4-4b90-96aa-64044060bb47{'x': 50, 'y': 1063}div{'height': 39, 'width': 465} 【4.11】延时等待在 Selenium 中,get() 方法会在网页框架加载结束后结束执行,某些页面有额外的 Ajax 请求,若此时立即获取 page_source,可能并不是浏览器完全加载完成的页面,这里需要延时等待一定时间,确保节点已经加载出来 【4.11.1】隐式等待当查找节点的时候,节点并没有立即出现,隐式等待将等待一段时间再查找该节点,使用 implicitly_wait() 方法可以实现隐式等待12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.implicitly_wait(10)browser.get('https://www.itrhx.com')readmore = browser.find_element_by_class_name('readmore')print(readmore) 【4.11.2】显式等待指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就立即返回查找的节点,果到了规定时间依然没有加载出该节点,则抛出超时异常123456789101112from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.implicitly_wait(10)browser.get('https://www.itrhx.com')wait = WebDriverWait(browser, 10)footer_info = wait.until(EC.presence_of_element_located((By.ID, 'footer')))print(footer_info) 引入 WebDriverWait 对象,指定最长等待时间,调用它的 until() 方法,传入要等待条件 expected_conditions。比如,这里传入了 presence_of_element_located 这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是 ID 为 footer 的节点。 这样可以做到的效果就是,在 10 秒内如果 ID 为 footer 的节点成功加载出来,就返回该节点;如果超过 10 秒还没有加载出来,就抛出异常。 加载成功时输出结果:1<selenium.webdriver.remote.webelement.WebElement (session=\"4ca7015891fded627ab680d9462e9361\", element=\"3a80235c-9824-420b-b827-662638422765\")> 加载失败时输出结果:12345TimeoutException Traceback (most recent call last)<ipython-input-4-f3d73973b223> in <module>() 7 browser.get('https://www.itrhx.com') 8 wait = WebDriverWait(browser, 10)----> 9 input = wait.until(EC.presence_of_element_located((By.ID, 'footer'))) 【4.12】Cookies使用 Selenium,可以方便地对 Cookies 进行获取、添加、删除等操作:12345678910from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.zhihu.com/explore')print(browser.get_cookies())browser.add_cookie({'name': 'TRHX', 'domain': 'www.zhihu.com', 'value': 'germey'})print(browser.get_cookies())browser.delete_all_cookies()print(browser.get_cookies()) 访问知乎,加载完成后,浏览器已经生成了 Cookies。调用 get_cookies() 方法获取所有的 Cookies。然后再添加一个 Cookie,传入一个字典,有 name、domain 和 value 等内容。接下来,再次获取所有的 Cookies。可以发现,结果就多了这一项新加的 Cookie。最后,调用 delete_all_cookies() 方法删除所有的 Cookies。再重新获取,发现结果就为空了输出结果:123[{'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '\"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741\"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}][{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'TRHX', 'path': '/', 'secure': True, 'value': 'germey'}, {'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '\"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741\"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}][{'domain': 'zhihu.com', 'expiry': 1644217741.489889, 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'WNOjpDbNmz36B4nG1lzSAuPdTyORMX6J'}] 【4.13】前进与后退使用 back() 方法后退,使用 forward() 方法前进,与浏览器的前进后退一样示例:123456789101112from selenium import webdriverimport timepath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com/')browser.get('https://www.baidu.com/')browser.get('https://www.zhihu.com/')browser.back()time.sleep(1)browser.forward()browser.close() 【4.14】选项卡和浏览器一样,在 Selenium 中也可以新建一个选项卡12345678910111213from selenium import webdriverimport timepath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')browser.execute_script('window.open()')print(browser.window_handles)browser.switch_to.window(browser.window_handles[1])browser.get('https://www.baidu.com')time.sleep(1)browser.switch_to.window(browser.window_handles[0])browser.get('https://www.zhihu.com') 首先访问我的博客,然后调用了 execute_script() 方法,传入 window.open() 这个 JavaScript 语句开启一个新的选项卡。再调用 window_handles 属性获取当前开启的所有选项卡,返回的是选项卡的代号列表。调用 switch_to_window() 方法来切换选项卡,其中参数是选项卡的代号。输出的选项卡代号列表:1['CDwindow-C9CADF1ED28CE44970655238552A8DCF', 'CDwindow-538D7F81E467746B7BB2D9D82E2D036E']","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"}]},{"title":"Python3 爬虫学习笔记 C03","slug":"A31-Python3-spider-C03","date":"2019-08-23T01:28:22.178Z","updated":"2019-09-24T12:43:59.900Z","comments":true,"path":"2019/08/23/A31-Python3-spider-C03/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A31-Python3-spider-C03/","excerpt":"Python3 爬虫学习笔记第三章 ——【Ajax 数据爬取】","text":"Python3 爬虫学习笔记第三章 ——【Ajax 数据爬取】 【3.1】Ajax 简介Ajax — Asynchronous Javascript And XML(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。可以在不重新加载整个网页的情况下,对网页的某部分进行更新。 【3.2】解析真实地址提取以豆瓣电影动作片排行榜为例,地址为:https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action= ,首先使用常用方法来爬取电影信息:12345678import requestsurl = 'https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action='headers = {\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0\"}response = requests.get(url, headers=headers)print(response.text) 得到的数据里面我们并没有找到电影相关信息:再次分析页面,发现鼠标下滑的时候,页面不刷新,URL 也不变,但是会加载新数据,那么此处就运用了 Ajax,可以使用抓包工具或者浏览器控制台来捕获 Ajax 接口,获取其真实地址,XHR 是 Ajax 特殊的请求类型,返回的是 json 数据,利用浏览器控制台过滤 XHR,随便点击一条请求,可以看到其 Request URL,也就是真实地址,点击 Preview 就可以看到返回的 json 数据。同样,我们可以使用 Fiddler 抓包软件抓取 Ajax 接口:分析其真实地址为:https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=20&limit=20 ,多下滑几次,只有 start 参数发生了改变,观察变化可知:每一次页面将多出20个电影信息,start 为从第几个电影开始,由此就不难进行数据抓取了 代码:1234567891011121314import requestsurl = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&'page = int(input('请输入想要第几页的数据:'))data = { 'start': (page - 1)*20, 'limit': '20',}headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}response = requests.get(url, params=data, headers=headers)print(response.text) 运行代码即可得到电影排行信息:","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Ajax","slug":"Ajax","permalink":"https://www.itrhx.com/tags/Ajax/"}]},{"title":"Python3 爬虫学习笔记 C02","slug":"A30-Python3-spider-C02","date":"2019-08-23T01:28:22.053Z","updated":"2019-09-24T12:40:04.967Z","comments":true,"path":"2019/08/23/A30-Python3-spider-C02/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/","excerpt":"Python3 爬虫学习笔记第二章 ——【基本库 requests 的使用】","text":"Python3 爬虫学习笔记第二章 ——【基本库 requests 的使用】 【2.1】 requests 简介在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库 requests 模块需要使用 pip install 命令安装安装,相比 urllib,它的 API 更加人性化,使用 requests 可以让 Cookies、登录验证、代理设置等操作更加简便,官网介绍:http://cn.python-requests.org 【2.2】 requests 基本用法示例:123456789import requestsr = requests.get('https://www.itrhx.com/')print(type(r))print(r.encoding)print(r.status_code)print(r.cookies)print(r.json)print(r.text)print(r.content) 输出结果:12345678910<class 'requests.models.Response'>utf-8200<RequestsCookieJar[]><bound method Response.json of <Response [200]>><!DOCTYPE html><html><head> <meta charset=\"utf-8\"> ...... r.encoding:服务器内容使用的文本编码; r.status_code:响应状态码,200 代表成功,4xx 代表客户端错误,5xx 服务器响应错误; r.cookies:返回 Cookies; r.json:Requests 内置 JSON 解码器; r.text:服务器响应内容,根据响应头部的字符编码自动解码; r.content:字节方式的响应体,自动解码 gzip 和 deflate 编码的响应。 【2.3】 requests 构建 GET 请求 【2.3.1】 基本用法示例:123456789import requestsdata = { 'name': 'TRHX', 'age': '20'}r = requests.get(\"http://httpbin.org/get\", params=data)print('编码后的URL:', r.url)print('字符串方式的响应体:', r.text) 输出结果:123456789101112131415编码后的URL: http://httpbin.org/get?name=TRHX&age=20字符串方式的响应体: { \"args\": { \"age\": \"20\", \"name\": \"TRHX\" }, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/get?name=TRHX&age=20\"} 【2.3.2】 二进制数据抓取以抓取 GitHub 站点图标为例:12345import requestsr = requests.get(\"https://github.com/favicon.ico\")with open('favicon.ico', 'wb') as f: f.write(r.content) 该代码将会保存站点图标到本地,其他的,比如音频,视频文件都是由二进制码组成的,皆可使用该方法 【2.3.3】 添加 headersheaders 的作用:部分页面禁止 Python 爬虫对其进行爬取,而添加 headers 就可以模拟成浏览器取访问网站,实现数据的爬取,headers 可以在任意网页 F12 检查控制台里面找到,headers 最重要的是 “User-Agent” 字段 以为例知乎,只有加了 headers 才能正常爬取,否则会返回 400 Bad Request 没有任何数据123456import requestsheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}r = requests.get(\"https://www.zhihu.com/explore\", headers=headers)print(r.text) 【2.4】 requests 构建 POST 请求示例:12345import requestsdata = {'name': 'TRHX', 'age': '20'}r = requests.post(\"http://httpbin.org/post\", data=data)print(r.text) 输出结果:1234567891011121314151617181920{ \"args\": {}, \"data\": \"\", \"files\": {}, \"form\": { \"age\": \"22\", \"name\": \"germey\" }, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Content-Length\": \"18\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"json\": null, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/post\"} 有关 POST 和 GET 两种请求的一些区别: POST 更加安全,不会作为 URL 的一部分,不会被缓存,保存在服务器日志、以及浏览器浏览记录中; POST 发送的数据更大,GET 有 URL 长度限制; POST 可以发送更多的数据类型,GET 只能发送 ASCII 字符; POST 比 GET 慢; POST 查询参数在 WebForms 保存,GET 查询参数在 QueryString 保存; POST 用数据的修改和写入,GET 一般用于搜索排序和筛选之类的操作。 【2.5】 requests 高级用法 【2.5.1】 上传文件示例:12345import requestsfiles = {'file': open('test.png', 'rb')}r = requests.post('http://httpbin.org/post', files=files)print(r.text) 输出结果:12345678910111213141516171819{ \"args\": {}, \"data\": \"\", \"files\": { \"file\": \"data:application/octet-stream;base64,iVBOR......\" }, \"form\": {}, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Content-Length\": \"81383\", \"Content-Type\": \"multipart/form-data; boundary=e36a8686cd77c79dc02bfe9d1b010f08\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"json\": null, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/post\"} 【2.5.2】 使用 Cookies对于需要登录后才能获取数据的网页,可以将账号登录的 Cookies 添加到 headers 来实现网页登录爬取,Cookies 可以抓包获取,代码示例:123456789import requestsheaders = { 'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Host': 'www.zhihu.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',}r = requests.get('https://www.zhihu.com', headers=headers)print(r.text) 【2.5.3】 会话维持 背景介绍:利用 get() 或者 post() 方法来模拟网页请求,相当于是不同的会话,可以理解为用两个浏览器打开了不同的网页; 运用场景:首先使用 post() 方法登录网页,然后再使用 get() 方法请求某个页面信息,如果不利用会话维持,将无法获取页面数据 维持方法:①两次请求设置一样的 cookies,缺点:繁琐;②使用 Session 对象。 Session 对象使用示例: 123456 import requestss = requests.Session()s.get('http://httpbin.org/cookies/set/number/123456789')r = s.get('http://httpbin.org/cookies')print(r.text) 输出结果成功获取到设置的 cookies:12345{ \"cookies\": { \"number\": \"123456789\" }} 【2.5.4】 SSL 证书验证 SSL 证书是数字证书的一种,由受信任的数字证书颁发机构 CA 在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,网站带有 HTTPS 就表明有 SSL 证书 requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,verify 参数可以控制是否检查此证书。如果不加 verify 参数,默认为 True,会自动验证。当一个页面的 SSL 证书没有被官方机构认证时,打开页面就会提示“您的连接不是私密连接”,如果没有设置 verify 参数,将会报以下错误:1requests.exceptions.SSLError: (\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",) 设置 verify 参数代码示例:1234import requestsresponse = requests.get('https://www.itrhx.com', verify=False)print(response.text) 【2.5.5】 设置代理为什么要设置代理:某些网页有反爬虫机制,频繁请求网页就会出现验证码等,还有可能直接封掉 IP,导致爬取失败;这种情况下就可以设置 proxies 参数。示例:12345678import requestsproxies = { 'http': 'http://10.10.1.10:1010', 'https': 'http://10.10.1.10:1020',}requests.get('https://www.itrhx.com', proxies=proxies) 免费代理可在西刺代理找到 【2.5.6】 超时设置与 urllib.request.urlopen() 类似,requests 也可以设置 timeout 参数,请求分为两个阶段:连接和读取 设置连接和读取时间总和: 1234import requestsr = requests.get('https://www.itrhx.com', timeout=1)print(r.status_code) 分别设置连接和读取时间:1234import requestsr = requests.get('https://www.itrhx.com', timeout=(5, 10))print(r.status_code) 永久等待:123456import requests# 两种方法实现# r = requests.get('https://www.itrhx.com')r = requests.get('https://www.itrhx.com', timeout=None)print(r.status_code)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"requests","slug":"requests","permalink":"https://www.itrhx.com/tags/requests/"}]},{"title":"Python3 爬虫学习笔记 C01","slug":"A29-Python3-spider-C01","date":"2019-08-23T01:28:21.841Z","updated":"2019-09-24T12:39:54.253Z","comments":true,"path":"2019/08/23/A29-Python3-spider-C01/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A29-Python3-spider-C01/","excerpt":"Python3 爬虫学习笔记第一章 ——【基本库 urllib 的使用】","text":"Python3 爬虫学习笔记第一章 ——【基本库 urllib 的使用】 【1.1】 urllib 简介在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库 urllib 库:Python 内置的 HTTP 请求库,无需额外安装即可使用;Python 2 中有 urllib 和 urllib2 两个库来实现请求的发送,Python 3 中统一为 urllib。官方文档:https://docs.python.org/3/library/urllib.html urllib 所包含的常用模块: urllib.request:模拟发送请求; urllib.error:异常处理模块,用于捕获异常; urllib.parse:解析、拆分、合并URL; urllib.robotparser:读取网站的 robots.txt 文件,判断哪些内容可以爬取。 urllib.request 所包含的常用方法: urllib.request.urlopen():打开网址URL,这可以是一个字符串或一个 Request对象; urllib.request.Request():在请求的时候传入一些 headers 等信息; urllib.request.urlretrieve():将获取的URL的内容写到文件目录中去。 urllib.error 所包含的两个异常: URLError:继承自 OSError 类,是 error 异常模块的基类,由 request 模块产生的异常都可以通过捕获这个类来处理。 HTTPError:是 URLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。 urllib.parse 所包含的常用方法: urllib.parse.urlencode():将字典参数序列化为 GET 请求参数; urllib.parse.parse_qs():将 GET 请求参数反序列化转回字典; urllib.parse.parse_qsl():将参数转化为元组组成的列表; urllib.parse.urlparse():对 URL 进行分段(返回6个结果); urllib.parse.urlunparse():对 URL 进行组合(长度必须为6); urllib.parse.urlsplit():对 URL 进行分段(不单独解析params部分,返回5个结果); urllib.parse.urlunsplit():对 URL 进行组合(长度必须为5); urllib.parse.urljoin():对 URL 进行组合(没有长度限制,给定两个参数,自动分析 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果); urllib.parse.quote():将内容转化为 URL 编码格式; urllib.parse.unquote():对 URL 进行解码。 urllib.robotparser 所包含的类: RobotFileParser:根据网站的 robots.txt 文件来判断一个爬取爬虫是否有权限来爬取这个网页 【1.2】 urllib.request 发送请求【1.2.1】 urllib.request.urlopen()【1.2.1.1】 基本使用方法urlopen() 函数的 API:1urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None) 基本使用:运行以下代码可得到 https://www.itrhx.com/ 的网页源代码:1234import urllib.requestresponse = urllib.request.urlopen('https://www.itrhx.com/')print(response.read().decode('utf-8')) 输出响应对象的类型和属性:1234567import urllib.requestresponse = urllib.request.urlopen('https://www.itrhx.com/')print(type(response)) # 响应类型print(response.status) # 返回结果的状态码,200代表请求成功print(response.getheaders()) # 响应的头信息print(response.getheader('Server')) # 获取响应头的 server 值 运行结果:1234<class 'http.client.HTTPResponse'>200[('Content-Type', 'text/html; charset=utf-8'), ('Server', 'GitHub.com'), ('Last-Modified', 'Sat, 17 Aug 2019 12:16:48 GMT'), ('ETag', '\"5d57f030-10863\"'), ('Access-Control-Allow-Origin', '*'), ('Expires', 'Sat, 17 Aug 2019 19:41:25 GMT'), ('Cache-Control', 'max-age=600'), ('X-Proxy-Cache', 'MISS'), ('X-GitHub-Request-Id', 'C748:735D:5B7461:619B95:5D58560B'), ('Content-Length', '67683'), ('Accept-Ranges', 'bytes'), ('Date', 'Sun, 18 Aug 2019 13:28:44 GMT'), ('Via', '1.1 varnish'), ('Age', '228'), ('Connection', 'close'), ('X-Served-By', 'cache-tyo19931-TYO'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '1'), ('X-Timer', 'S1566134924.190474,VS0,VE0'), ('Vary', 'Accept-Encoding'), ('X-Fastly-Request-ID', '25a69f8130fc9cae412d28990a724543d7d05e8b')]GitHub.com 【1.2.1.2】 添加参数根据 urlopen() 函数的 API 可知,除了最基本的 URL 参数以外,我们还可以传递其他内容,比如 data(附加数据)、timeout(超时时间)等,以下用 data 和 timeout 参数举例说明。 ● data 参数如果要添加 data 参数,需要使用 bytes 方法将参数转化为字节流编码格式的内容,即 bytes 类型。另外,如果传递了这个参数,则它的请求方式就不再是 GET 方式,而是 POST 方式。代码示例:123456import urllib.parseimport urllib.requestdata = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')response = urllib.request.urlopen('http://httpbin.org/post', data=data)print(response.read()) httpbin.org 站点提供 HTTP 请求测试,http://httpbin.org/post 用于测试 POST 请求,示例中传递一个值为 hello 的 word 参数。使用 bytes 方法,将其转码成 bytes(字节流)类型。该方法的第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode 方法来将参数字典转化为字符串;第二个参数指定编码格式为 utf8,运行结果:123456789101112131415161718b'{ \"args\": {}, \"data\": \"\", \"files\": {}, \"form\": { \"word\": \"hello\" }, \"headers\": { \"Accept-Encoding\": \"identity\", \"Content-Length\": \"10\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Python-urllib/3.6\" }, \"json\": null, \"origin\": \"171.115.101.10, 171.115.101.10\", \"url\": \"https://httpbin.org/post\"}' ● timeout 参数举例:1234import urllib.requestresponse = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) print(response.read()) 运行结果:12345678...During handling of the above exception, another exception occurred:Traceback (most recent call last): File \"C:/Users/Lenovo/Desktop/1.py\", line 2, in <module> response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) ...urllib.error.URLError: <urlopen error timed out> timeout 设置为0.1,0.1秒过后服务器没有响应,便会抛出 URLError 异常进阶:使用 try except 语句抛出异常 【1.2.2】 urllib.request.Request()Request() 方法可以在请求的时候传入一些 data、headers 等信息Request() 的构造方法:1class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None) 构造方法各个参数的解释: url:用于请求 URL,这是必传参数,其他都是可选参数。 data:如果要传,必须传 bytes(字节流)类型的。如果它是字典,可以先用 urllib.parse 模块里的 urlencode() 编码。 headers:是一个字典,它就是请求头,可以在构造请求时通过 headers 参数直接构造,也可以通过调用请求实例的 add_header() 方法添加。添加请求头最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。 origin_req_host:指的是请求方的 host 名称或者 IP 地址。 unverifiable:表示这个请求是否是无法验证的,默认是 False,意思就是说用户没有足够权限来选择接收这个请求的结果。例如,我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。 method:是一个字符串,用来指示请求使用的方法,比如 GET、POST 和 PUT 等。 简单举例:1234567891011121314import urllib.requestimport urllib.parseurl = 'http://www.baidu.com/'# 定制要伪装的头部headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}# 构建请求对象request = urllib.request.Request(url=url, headers=headers)# 发送请求response = urllib.request.urlopen(request)print(response.read().decode()) 【1.2.3】 urllib.request.urlretrieve()将获取到的 URL 内容保存到当前文件夹,简单举例:123456789import urllib.requesturl = 'https://www.itrhx.com/images/trhx.png'# response = urllib.request.urlopen(image_url)# with open('trhx.png', 'wb') as fp:# fp.write(response.read())urllib.request.urlretrieve(url, 'trhx.png') 【1.3】 urllib.error 异常处理【1.3.1】 URLError如果打开一个不存在的页面,就会出现 URLError 错误,该错误有一个 reason 属性,用于返回错误的原因。简单举例:12345from urllib import request, error try: response = request.urlopen('https://www.itrhx.com/index/') except error.URLError as e: print(e.reason) 输出结果:1Not Found 【1.3.2】 HTTPErrorURLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。它有如下3个属性: code:返回 HTTP 状态码,比如 404 表示网页不存在,500 表示服务器内部错误等。 reason:同父类一样,用于返回错误的原因。 headers:返回请求头。 简单举例:12345from urllib import request, error try: response = request.urlopen('https://www.itrhx.com/index/') except error.HTTPError as e: print(e.code, e.reason, e.headers) 输出结果:123456789101112131415161718404 Not Found Content-Type: text/html; charset=utf-8Server: GitHub.comETag: \"5d57f030-7f2\"Access-Control-Allow-Origin: *X-Proxy-Cache: MISSX-GitHub-Request-Id: 4B46:2F5D:6DE0F1:755BB2:5D5964C5Content-Length: 2034Accept-Ranges: bytesDate: Sun, 18 Aug 2019 14:50:41 GMTVia: 1.1 varnishAge: 252Connection: closeX-Served-By: cache-tyo19951-TYOX-Cache: HITX-Cache-Hits: 1X-Timer: S1566139842.563134,VS0,VE0Vary: Accept-EncodingX-Fastly-Request-ID: e9eb0a507be66a866bfaa7c5cc2e1c53b1f7ccab 【1.3.3】 进阶用法因为 URLError 是 HTTPError 的父类,所以可以先选择捕获子类的错误,再去捕获父类的错误,前面的代码改进:12345678910from urllib import request, error ​try: response = request.urlopen('https://www.itrhx.com/index/') except error.HTTPError as e: print(e.reason, e.code, e.headers) except error.URLError as e: print(e.reason) else: print('Request Successfully') 【1.4】 urllib.parse 解析 URL【1.4.1】 urllib.parse.urlencode()将字典参数序列化为 GET 请求参数,示例:12345678from urllib.parse import urlencodedata = { 'ie': 'utf-8', 'wd': 'TRHX',}base_url = 'http://www.baidu.com?'url = base_url + urlencode(data)print(url) 输出结果:1http://www.baidu.com?ie=utf-8&wd=TRHX 【1.4.2】 urllib.parse.parse_qs()与 urlencode() 相反,将 GET 请求参数反序列化转回字典,示例:123from urllib.parse import parse_qsquery = 'name=TRHX&age=20'print(parse_qs(query)) 输出结果:1{'name': ['TRHX'], 'age': ['20']} 【1.4.3】 urllib.parse.parse_qsl()将参数转化为元组组成的列表,示例:123from urllib.parse import parse_qslquery = 'name=TRHX&age=20'print(parse_qsl(query)) 输出 结果:1[('name', 'TRHX'), ('age', '20')] 【1.4.4】 urllib.parse.urlparse()对 URL 进行分段,返回 6 个结果,示例:123from urllib.parse import urlparseresult = urlparse('http://www.baidu.com/index.html;user?id=5#comment')print(type(result), result) 输出结果:1<class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment') 返回结果为 ParseResult 类型的对象,含 scheme、netloc、path、params、query 和 fragment 6 个部分,依次代表协议、域名、路径、参数、查询条件、锚点 【1.4.5】 urllib.parse.urlunparse()与 urlparse() 相反,对 URL 进行组合,传入的参数是一个可迭代对象,长度必须是 6,否则会抛出参数数量不足或者过多的问题,示例:123from urllib.parse import urlunparse data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment'] print(urlunparse(data)) 输出结果:1http://www.baidu.com/index.html;user?a=6#comment 【1.4.6】 urllib.parse.urlsplit()与 urlparse() 方法相似,但是它不再单独解析 params 部分,只返回 5 个结果。params 会合并到 path 中,示例:123from urllib.parse import urlsplit result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment') print(result) 输出结果:1SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment') 【1.4.7】 urllib.parse.urlunsplit()与 urlunparse() 方法类似,对 URL 进行组合,传入的参数也是一个可迭代对象,长度必须为 5,示例:123from urllib.parse import urlunsplit data = ['http', 'www.baidu.com', 'index.html', 'a=6', 'comment'] print(urlunsplit(data)) 输出结果:1http://www.baidu.com/index.html?a=6#comment 【1.4.8】 urllib.parse.urljoin()对 URL 进行组合,提供两个 URL 作为两个参数,将会自动分析 URL 的 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果,示例:123456789from urllib.parse import urljoin print(urljoin('http://www.baidu.com', 'friends.html')) print(urljoin('http://www.baidu.com', 'https://www.itrhx.com/friends.html')) print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html')) print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html?id=2')) print(urljoin('http://www.baidu.com?wd=trhx', 'https://www.itrhx.com/index.html')) print(urljoin('http://www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com#comment', '?category=2')) 输出结果:12345678http://www.baidu.com/friends.htmlhttps://www.itrhx.com/friends.htmlhttps://www.itrhx.com/friends.htmlhttps://www.itrhx.com/friends.html?id=2https://www.itrhx.com/index.htmlhttp://www.baidu.com?category=2#commentwww.baidu.com?category=2#commentwww.baidu.com?category=2 【1.4.9】 urllib.parse.quote()将内容转化为 URL 编码的格式。当 URL 中带有中文参数时,可以将中文字符转化为 URL 编码,示例:1234from urllib.parse import quotekeyword = '中国' url = 'https://www.baidu.com/s?wd=' + quote(keyword) print(url) 输出结果:1https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD 【1.4.10】 urllib.parse.unquote()与 quote() 方法相反,对 URL 进行解码,示例:123from urllib.parse import unquote url = 'https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD' print(unquote(url)) 输出结果:1https://www.baidu.com/s?wd=中国 【1.5】 urllib.robotparser 爬取权限判断【1.5.1】 Robots 协议简介 Robots 协议即爬虫协议,用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。它通常是一个叫作 robots.txt 的文本文件,一般放在网站的根目录下。 robots.txt 基本格式:123User-agent:Disallow:Allow: User-agent 为搜索爬虫的名称,设置为 * 则表示对任何爬虫皆有效; Disallow 指定了不允许抓取的目录,设置为 / 则代表不允许抓取所有页面; Allow 指定了允许抓取的目录,一般和 Disallow 一起使用,一般不会单独使用,用来排除某些限制。 一些常见的搜索爬虫名称及其对应的网站: 爬虫名称 网站名称 网站地址 BaiduSpider 百度 www.baidu.com Googlebot 谷歌 www.google.com 360Spider 360 www.so.com Sogouspider 搜狗 www.sogou.com YodaoBot 有道 www.youdao.com Bingbot 必应 www.bing.com Yahoo! Slurp 雅虎 www.yahoo.com ia_archiver Alexa www.alexa.cn Scooter altavista www.altavista.com 【1.5.2】 RobotFileParser 类常用方法RobotFileParser 类的声明:1urllib.robotparser.RobotFileParser(url='') 常用方法及其解释: set_url:用来设置 robots.txt 文件的链接。如果在创建 RobotFileParser对象时传入了链接,那么就不需要再用这种方法了。 read:读取 robots.txt 文件并进行分析。此方法执行一个读取和分析操作,若不调用此方法,接下来的判断都会为 False,这个方法不会返回任何内容,但是执行了读取操作。 parse:解析 robots.txt 文件,传入的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容。 can_fetch:该方法传入两个参数,第一个是 User-agent,第二个是要抓取的 URL。返回的内容是该搜索引擎是否可以抓取这个 URL,返回结果是 True 或 False。 mtime:返回的是上次抓取和分析 robots.txt 的时间,此方法可以定期检查来抓取最新的 robots.txt。 modified:将当前时间设置为上次抓取和分析 robots.txt 的时间。 以简书为例:123456from urllib.robotparser import RobotFileParserrp = RobotFileParser()rp.set_url('http://www.jianshu.com/robots.txt')rp.read()print(rp.can_fetch('*', 'https://www.jianshu.com/p/6d9527300b4c'))print(rp.can_fetch('*', \"http://www.jianshu.com/search?q=python&page=1&type=collections\")) 输出结果:12FalseFalse","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"urllib","slug":"urllib","permalink":"https://www.itrhx.com/tags/urllib/"}]},{"title":"一个 JS 脚本实现网站预加载,提升页面加载速度","slug":"A24-instant.page","date":"2019-08-23T01:27:49.948Z","updated":"2019-09-09T13:44:52.383Z","comments":true,"path":"2019/08/23/A24-instant.page/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A24-instant.page/","excerpt":"instant.page 使用即时预加载技术,在用户点击之前预先加载页面。当用户的鼠标悬停在一个链接上超过 65 毫秒时,浏览器会对此页面进行预加载,当用户点击链接后,就从预加载的缓存中直接读取页面内容,从而达到缩短页面加载时间的目的。","text":"instant.page 使用即时预加载技术,在用户点击之前预先加载页面。当用户的鼠标悬停在一个链接上超过 65 毫秒时,浏览器会对此页面进行预加载,当用户点击链接后,就从预加载的缓存中直接读取页面内容,从而达到缩短页面加载时间的目的。 以我博客为例,使用了这项技术后,当鼠标在一个链接停留超过 65 毫秒时,Network 里可以看见相关文章已经预加载出来了,而停留时间过短就不会预加载(红色部分,状态为 canceled) 使用方法:将以下HTML代码放在</ body> 之前即可:1<script src=\"//instant.page/1.2.2\" type=\"module\" integrity=\"sha384-2xV8M5griQmzyiY3CDqh1dn4z3llDVqZDqzjzcY+jCBCk/a5fXJmuZ/40JJAPeoU\"></script> 但是此脚本是官方的,储存在国外服务器,对国内访问不太友好,可以将该JS脚本储存到自己的服务器上,点此获取该JS脚本,然后再根据以下格式在</ body> 之前引用:1<script src=\"`存放路径`/instantclick-1.2.2.js\" type=\"module\"></script> 也可以直接使用我的,使用 jsDeliver CDN 加速,速度还可以:1<script src=\"https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.2/js/instantclick-1.2.2.js\" type=\"module\"></script> 参考资料:《网站预加载 JS 脚本 instant.page》——by 左岸 ;instant.page官网","categories":[{"name":"WEB前端","slug":"WEB前端","permalink":"https://www.itrhx.com/categories/WEB前端/"}],"tags":[{"name":"instant.page","slug":"instant-page","permalink":"https://www.itrhx.com/tags/instant-page/"},{"name":"JS 预加载","slug":"JS-预加载","permalink":"https://www.itrhx.com/tags/JS-预加载/"}]},{"title":"网站ICP备案和公安备案流程","slug":"A23-beian","date":"2019-08-23T01:27:49.803Z","updated":"2019-12-31T11:24:22.073Z","comments":true,"path":"2019/08/23/A23-beian/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A23-beian/","excerpt":"","text":"网站备案分为ICP备案和公安备案 ICP备案:ICP备案的目的就是为了防止在网上从事非法的网站经营活动,打击不良互联网信息的传播,如果网站不备案的话,很有可能被查处以后关停。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》条例,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款或关闭。 公安备案:网站备案是根据国家法律法规需要网站的所有者向国家有关部门申请的备案,公安局备案是其中一种。公安局备案一般按照各地公安机关指定的地点和方式进行,操作流程会比ICP备案流程简单,主要是已登记为主。 以百度官网为例,其中京公安网备11000002000001就是公安备案,京ICP证030173号就是ICP备案 – ICP备案 一般在域名服务商那里都会有代备案系统,下面以阿里云为例,进入备案系统: 1、填写信息验证备案类型备案主办单位填写,个人就选个人,企业就选企业,按照实际信息填写: 2、产品验证对搭建备案网站的云服务器进行验证,如果你在阿里云购买了相关产品,就选择相应的产品类型和实例进行验证,也可以勾选已有备案服务号,填写服务号进行验证,备案服务号可以通过备案控制台进行申请,具体操作可以参考官方文档《申请备案服务号》,也有的小伙伴没有在任何地方购买过服务器等相关产品,比如单纯搭建一个 Github Pages + Hexo 轻量级的个人博客,这种博客没有后端,不需要服务器,但是要备案怎么办?这种情况也好解决,去某宝买一个服务号就行了。 3、填写网站信息填写网站信息以及办理备案的个人或者单位的真实信息,在填写网站名称的时候要特别注意!特别注意!特别注意!不满足要求的话是会被打回的!不能使用姓名、地名、成语、不能包含公司、组织等企业性质的词语……具体要求可以参考官方文档《填写主体信息和网站信息》。 4、上传资料根据要求,上传证件照片或证件彩色扫描件。身份证好说,拍好了上传就行了,注意《网站备案信息真实性核验单》需要你下载并打印在一张A4纸上,使用黑色签字笔填写,不能涂改,具体可参照所给的示例进行填写,填写完成后再拍照上传。企业网站类似,提交备案后会在一个工作日内进行初审。 5、人脸核验或幕布拍照核验根据不同地域管局要求及核验平台的支持情况,使用人脸识别进行核验,或者申请专用幕布进行幕布拍照核验 地区 核验要求 上海、福建地区用户 需使用阿里云APP进行人脸核验。如果使用PC端发起的备案申请,请根据界面提示下载阿里云APP进行人脸核验。 广东、辽宁、安徽、重庆地区用户 首次备案、新增网站:支持使用阿里云APP进行人脸核验或通过阿里云备案平台(PC端)进行幕布拍照核验。其他备案类型:需通过阿里云备案平台(PC端)进行幕布拍照核验。 其他地区用户 通过阿里云备案平台(PC端)进行幕布拍照核验。 以幕布拍照核验为例,如果你没有阿里云的幕布,就需要申请幕布(免费的),邮寄很快,大约两三天就到了,等收到幕布后,按照要求进行拍照,一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!不合格依旧会被打回!拍照完成后上传即可。 6、提交管局、短信核验当照片审核通过后,就会提交到管局,工信部要求部分省市成为手机号码短信核验试点省市,相应省市的用户在阿里云备案平台提交备案申请且初审完成后,会收到工信部发送的核验短信,短信包含验证码和验证地址,需要在收到短信的24小时内完成短信核验,备案申请才能进入管局审核。需短信核验省份: 2017年12月18日起:天津、甘肃、西藏、宁夏、海南、新疆、青海被列为试点省份。 2018年9月10日起:浙江、四川、福建、陕西、重庆、广西、云南被列为试点省份。 2018年9月24日起:山东、河南、安徽、湖南、山西、黑龙江、内蒙古、湖北被列为试点省份。 7、ICP备案完成整个备案过程中会有阿里云的客服打电话给你,进行信息确认,备案申请信息成功提交管局系统后,管局审核一般为 3 - 20 个工作日(亲测很快,不到一个周就通过了),审核通过后会收到阿里云的邮件通知。 – 公安备案 公安备案个人觉得比ICP备案还要麻烦,自己在公安备案的时候,最开始申请了一个月也没给我处理(大概是地方原因,所在的市比较小,估计都没几个人办过网站,网警也不太负责),与ICP备案最大的不同,如果你是交互式网站的话,公安备案是需要你去公安机关当面审核的,这也是比较麻烦的一点。 1、用户注册、登录登录全国互联网安全管理服务平台,选择联网备案登录,注册账号并登录 2、新办网站备案申请点击新办网站申请,按实填写网站开办主体,上传身份证正反照和手持身份证件照。 3、填写网站基本信息按实填写网站基本信息,需要注意的地方: IP:IP地址为阿里云/腾讯云的公网IP地址,请不要填写内网IP。 域名证书:以阿里云为例,进入【域名控制台】,点击域名后面的【管理】,选择【域名证书下载】即可,其它服务商类似。 网络接入/域名注册服务商:若办理公安备案的域名是通过阿里云完成的工信部备案,则按照以下填写:网络接入服务商: 接入商所属地区管辖:境内 接入商所属区域 :浙江省 杭州市 滨江区 名称:阿里云计算有限公司 网站接入方式:租赁虚拟空间 域名注册服务商: 域名商所属地区管辖:境内 域名服务商所属区域:浙江省 杭州市 余杭区 名称:阿里云计算有限公司(原万网) 也可以通过点击后面的查询网络接入\\域名注册服务商直接选择相应服务商,其他服务商类似 服务类型:交互式服务指:为互联网用户提供信息发布、交流互动等服务,包括但不限于论坛、博客、微博、网络购物、网上支付等服务类型,此项选择是否提供互联网交互服务将会直接影响到后面是否需要去公安局当面核验,若选择是,当地网警会打电话叫你去公安局当面核验,还需要填写《交互式服务安全检查表》等各种文件,总之是比较麻烦的,个人小网站,博客什么的建议选择否,选择www服务,这样的话不用去当面核验,审核下来也比较快,企业单位用户建议选择交互式。 其他信息如实填写即可! 4、填写网站负责人信息填写网站安全负责人和网站应急联络人相关信息,网站应急联络人直接勾选同主体负责人后会自动填入。 5、同意责任书并提交审核《互联网信息服务单位网络安全责任告知书》有30秒的强制阅读时间,建议认真阅读一下告知书的内容。然后勾选我已阅读,点击提交即可。随后可以看到审核状态,不同地区政策有所不同,会有当地的网警联系网站负责人的,审核通过后记得在网站首页底部张贴公安机关核发的备案图标!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"},{"name":"WEB前端","slug":"WEB前端","permalink":"https://www.itrhx.com/categories/WEB前端/"}],"tags":[{"name":"ICP备案","slug":"ICP备案","permalink":"https://www.itrhx.com/tags/ICP备案/"},{"name":"公安备案","slug":"公安备案","permalink":"https://www.itrhx.com/tags/公安备案/"}]},{"title":"恶意刷留言者——你是什么垃圾?","slug":"A25-SB","date":"2019-08-23T01:27:46.962Z","updated":"2019-09-09T14:09:57.901Z","comments":true,"path":"2019/08/23/A25-SB/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A25-SB/","excerpt":"","text":"有一种动物,自认为自己技术了得,实则和CXK差不多,以攻击他人为乐,这种动物称为程序员中的垃圾,哦!不,这种动物称不上程序员! 这个周连续被人刷垃圾评论,具体开始时间不记得了,不想多说什么,太多的文字用在垃圾身上简直是玷污中华上下五千年的文化,只问一句,你是什么垃圾? 随便提一句,这家伙连我的情侣博客一起刷的,真是让人大跌眼镜啊,估计自己没女朋友吧,见不得别人好,悲催啊,不知道又是哪个学校,哪个公司,哪个家庭摊上了这种垃圾。 请问你是什么垃圾?垃圾分类,从我做起!","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"垃圾","slug":"垃圾","permalink":"https://www.itrhx.com/tags/垃圾/"}]},{"title":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS","slug":"A28-hexo-add-https","date":"2019-08-11T14:16:09.175Z","updated":"2019-12-31T15:24:54.792Z","comments":true,"path":"2019/08/11/A28-hexo-add-https/","link":"","permalink":"https://www.itrhx.com/2019/08/11/A28-hexo-add-https/","excerpt":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS","text":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。 HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。 目前大多数基于 GitHub Pages 的 Hexo 博客都是利用 CloudFlare 的 CDN 中转来启用 HTTPS 的,实现方法可以参考我的文章:《利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持》,这样的做法确实可以起到开启HTTPS的目的,但是这样做也有弊端,你会发现 CDN 中转,国外访问的话,可以起到加速的作用,但是国内访问反而速度降低了,还不如直接连接GitHub呢 其实 GitHub 官方是支持自定义域名开启 HTTPS 的,之前我和大多数人一样,以为只有 GitHub Pages 自带的域名(xxx.github.io)才能开启 HTTPS,直到有一天我发现了官方在2018年5月1日发表的博客:《Custom domains on GitHub Pages gain support for HTTPS》,大概讲的意思就是从8月份开始, GitHub Pages 上的自定义域名也能开启 HTTPS 了,下面就具体介绍一下如何实现 如果你以前域名的记录类型是 CNAME 方式,那么就不需要做任何更改如果你以前域名的记录类型是 A 方式,那么就需要把记录值指向以下IP地址: 185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153 修改好记录值后,我们需要再次来到你博客的 GitHub 仓库,在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】,注意,如果此时你不能勾选,请删除【Custom domain】里面你的域名并点击【Save】保存,刷新网页后就可以勾选了,然后在把域名填进去并保存即可,短时间可能会出现不安全的提示,这是因为加密证书大概一个小时左右才会生效,等一会儿就好了 最后贴一个我的域名解析,可作为参考:","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"HTTPS","slug":"HTTPS","permalink":"https://www.itrhx.com/tags/HTTPS/"}]},{"title":"Github+jsDelivr+PicGo 打造稳定快速、高效免费图床","slug":"A27-image-hosting","date":"2019-07-31T16:02:08.497Z","updated":"2019-12-31T15:24:47.016Z","comments":true,"path":"2019/08/01/A27-image-hosting/","link":"","permalink":"https://www.itrhx.com/2019/08/01/A27-image-hosting/","excerpt":"","text":"– 前言图床是个啥东西就不用过多介绍了,先来对比一下各路图床: 微博图床:以前用的人比较多,从2019年4月开始开启了防盗链,凉凉 SM.MS:运营四年多了,也变得越来越慢了,到了晚上直接打不开图片,速度堪忧 其他小众图床:随时有挂掉的风险 Imgur等国外图床:国内访问速度太慢,随时有被墙的风险 大厂储存服务:例如七牛云、又拍云、腾讯云COS、阿里云OSS等,容量限制,操作繁琐,又是实名认证又是域名备案的,麻烦,而且还要花钱(有钱又不怕麻烦的当我没说) 因此,GitHub 图床是个不错的选择,利用 jsDelivr CDN 加速访问(jsDelivr 是一个免费开源的 CDN 解决方案),PicGo 工具一键上传,操作简单高效,GitHub 和 jsDelivr 都是大厂,不用担心跑路问题,不用担心速度和容量问题,而且完全免费,可以说是目前免费图床的最佳解决方案! – 新建GitHub仓库登录/注册GitHub,新建一个仓库,填写好仓库名,仓库描述,根据需求选择是否为仓库初始化一个README.md描述文件 – 生成一个Token在主页依次选择【Settings】-【Developer settings】-【Personal access tokens】-【Generate new token】,填写好描述,勾选【repo】,然后点击【Generate token】生成一个Token,注意这个Token只会显示一次,自己先保存下来,或者等后面配置好PicGo后再关闭此网页 – 配置PicGo前往下载PicGo,安装好后开始配置图床 设定仓库名:按照【用户名/图床仓库名】的格式填写 设定分支名:【master】 设定Token:粘贴之前生成的【Token】 指定存储路径:填写想要储存的路径,如【ITRHX-PIC/】,这样就会在仓库下创建一个名为 ITRHX-PIC 的文件夹,图片将会储存在此文件夹中 设定自定义域名:它的作用是,在图片上传后,PicGo 会按照【自定义域名+储存路径+上传的图片名】的方式生成访问链接,并放到粘贴板上,因为我们要使用 jsDelivr 加速访问,所以可以设置为【https://cdn.jsdelivr.net/gh/用户名/图床仓库名 】,上传完毕后,我们就可以通过【https://cdn.jsdelivr.net/gh/用户名/图床仓库名/图片路径 】加速访问我们的图片了,比如上图的图片链接为:https://cdn.jsdelivr.net/gh/TRHX/ImageHosting/ITRHX-PIC/A27/08.png 关于 jsDelivr 具体是如何引用资源的可以参考我的另一篇博客:《免费CDN:jsDelivr+Github》 – 进行高效创作配置好PicGo后,我们就可以进行高效创作了,将图片拖拽到上传区,将会自动上传并复制访问链接,将链接粘贴到博文中就行了,访问速度杠杠的,此外PicGo还有相册功能,可以对已上传的图片进行删除,修改链接等快捷操作,PicGo还可以生成不同格式的链接、支持批量上传、快捷键上传、自定义链接格式、上传前重命名等,更多功能自己去探索吧!","categories":[{"name":"图床","slug":"图床","permalink":"https://www.itrhx.com/categories/图床/"}],"tags":[{"name":"jsDelivr","slug":"jsDelivr","permalink":"https://www.itrhx.com/tags/jsDelivr/"},{"name":"图床","slug":"图床","permalink":"https://www.itrhx.com/tags/图床/"},{"name":"PicGo","slug":"PicGo","permalink":"https://www.itrhx.com/tags/PicGo/"}]},{"title":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持","slug":"A26-hexo-add-https","date":"2019-07-31T03:48:43.215Z","updated":"2019-12-29T07:20:24.370Z","comments":true,"path":"2019/07/31/A26-hexo-add-https/","link":"","permalink":"https://www.itrhx.com/2019/07/31/A26-hexo-add-https/","excerpt":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持","text":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持 HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。 HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。 – 前言GitHub Pages 自带的域名(xxx.github.io)支持开启 https 服务,可以在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】即可,但是如果你设置了自定义域名的话,就比较复杂了,因为 hexo 博客是托管在 GitHub 上的,没有自己的服务器,因此也不支持上传 SSL 证书,从2018年5月1日起,GitHub官方也支持自定义域名开启https了,实现方法可参考我的文章:《利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS》,另外一种方法就是利用 Cloudflare 的 CDN 中转来启用 HTTPS,这种方法的弊端就是国内访问速度可能会变慢,本文主要讲述这种方法 Cloudflare 是一家美国的跨国科技企业,以向客户提供网站安全管理、性能优化及相关的技术支持为主要业务,它提供了免费的 https 服务,注意不是应用SSL证书,实现原理:用户到CDN服务器的连接为 https 方式,而CDN服务器到 GithubPages 服务器的连接为 http 方式,在CDN服务器那里加上反向代理 – 注册 Cloudflare到 Cloudflare官网 注册账号 – 添加站点添加你的站点,一直下一步即可 如果你已经在域名服务商那里解析过域名的话,之后就会出现你域名的解析列表,如果还没有解析过,可以参考《为hexo博客配置个性域名》 –修改DNS点击下一步 Cloudflare 会提供给你两个 DNS 地址 到域名服务商那里修改DNS,以阿里云为例,依次选择【控制台】-【域名】,选择你的域名,点击【管理】-【修改DNS】,将上面 Cloudflare 提供的两个 DNS 地址填进去,会过几分钟才生效 –开启 HTTPS在 Cloudflare 管理页面,点击【Crypto】选项,选择 SSL 的模式为【full】,注意:在CloudFlare 上激活站点后,可能需要24小时才能颁发新证书,耐心等待即可 关于三种模式 Flexible、Full、Full (Strict) 的区别: Flexible:访客与 Cloudflare 之间是加密的,Cloudflare 到站点服务器是不加密的 Full:访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它不会验证你服务器上的证书是否合法,因此你可以在你服务器上安装任何证书,包括自签名证书 Full (strict):访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它会验证你服务器上的证书是否合法,你必须在你的服务器上安装有可信赖的CA证书,并且这个证书必须是未过期,包含有域名等信息的 至此,我们的域名就支持 https 访问了,但是当用户输入 http://xxxxxx 访问时,浏览器依旧会以 http 协议来访问,并不会跳转到 https,这时候就需要利用重定向来解决了 –重定向强制 HTTPSCloudflare 提供了一个名叫 Page Rules 的页面规则的功能,我们可以利用此功能对 URL 做一些处理,当用户访问是 HTTP 的时候重定向到 HTTPS,点击【Page Rules】选项,点击【Create Page Rules】,新建如下规则并保存即可 现在我们的 Hexo 博客就实现了全站 HTTPS!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"HTTPS","slug":"HTTPS","permalink":"https://www.itrhx.com/tags/HTTPS/"}]},{"title":"Eclipse 通过 JDBC 连接 SQL Server","slug":"A22-eclipse-connects-to-sql","date":"2019-05-13T18:21:23.785Z","updated":"2019-09-09T14:02:06.957Z","comments":true,"path":"2019/05/14/A22-eclipse-connects-to-sql/","link":"","permalink":"https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/","excerpt":"","text":"本文用到的软件版本以及相关环境: Eclipse Photon Release (4.8.0)JDK-10.0.2SQL Server 2012 1.配置 SQL Server 2012打开 SQL Server Management Studio,使用 SQL Server 身份验证 登录: 如果在安装 SQL Server 2012 时选用了Windows身份验证登录方式,则需要重新设置,设置方法参考:《SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】》 登录成功后,打开 SQL Server 配置管理器: 在左边找到 SQL Server 网络配置,点击【你的数据库名】的协议,将右边栏的 Shared Memory、Named Pipes、TCP/IP 全部右键选择启用: 双击 TCP/IP(或者右键选择属性),选择【IP地址】,将【IP1】和【IP10】的【IP地址】设为 127.0.0.1 将所有【IPx】(IP1、IP10、IP11、IP12等)的【已启用】设为是 下拉到窗口底部,将 【IPAll】 中的【TCP端口】设成 1433,其余不变 2.开启 Telnet 服务打开【控制面板】,选择【程序】,点击【启用或关闭 Windows 功能】,找到【Telnet Client】勾选并保存,Windows 7 或者以下的版本则勾选【Telnet 服务器】和【Telnet 客户端】 3.测试1433端口是否打开 运行cmd,输入 telnet 127.0.0.1 1433,若提示连接失败,则说明1433端口没有打开,需要重新进行以上配置,若连接成功,则显示如下: 4.下载JDBC 点击此处下载各个版本JDBC,不同版本的JDBC驱动程序适用的JAR不同,与不同版本的SQL兼容性也不同,具体参考《Microsoft SQL Server JDBC 驱动程序支持矩阵》,比如使用 SQL Server 2012 我们可以下载6.0的版本,下载sqljdbc_6.0.8112.200_chs.tar.gz文件,解压后可以找到sqljdbc41.jar与sqljdbc42.jar文件,使用时要注意自己JDK是哪个版本的,1.80以上的则对应 sqljdbc42.jar 类库 5.Eclipse 连接 SQL Server将 sqljdbc41.jar 或者 sqljdbc42.jar 放到一个文件夹下,打开 Eclipse,在需要连接数据库的项目里,右键【src】,选择【Build Path】、【Configure Build Path…】,在弹出的窗口选择【Libraries】,选择【Modulepath】,单击【Add External JARs…】,找到下载的 sqljdbc41.jar 或者 sqljdbc42.jar 文件并打开,然后【Apply and Close】保存 6.测试连接打开 SQL Server 2012,在其中新建数据库 test Eclipse中,在项目下新建一个package,再新建一个class,用于测试数据库的连接:12345678910111213141516171819202122package test;import java.sql.*;public class Main { public static void main(String [] args) { String driverName=\"com.microsoft.sqlserver.jdbc.SQLServerDriver\"; String dbURL=\"jdbc:sqlserver://localhost:1433;DatabaseName=test\"; //要连接的数据库名 String userName=\"sa\"; //数据库用户名 String userPwd=\"000000\"; //数据库密码 try { Class.forName(driverName); Connection dbConn=DriverManager.getConnection(dbURL,userName,userPwd); System.out.println(\"连接数据库成功\"); } catch(Exception e) { e.printStackTrace(); System.out.print(\"连接失败\"); } } } 如果以上所有操作正确,就能成功连接数据库了:","categories":[{"name":"Java","slug":"Java","permalink":"https://www.itrhx.com/categories/Java/"}],"tags":[{"name":"JDBC","slug":"JDBC","permalink":"https://www.itrhx.com/tags/JDBC/"},{"name":"SQL Server 2012","slug":"SQL-Server-2012","permalink":"https://www.itrhx.com/tags/SQL-Server-2012/"},{"name":"Elicpse","slug":"Elicpse","permalink":"https://www.itrhx.com/tags/Elicpse/"}]},{"title":"Python PEP8 代码规范常见问题及解决方法","slug":"A21-PEP8","date":"2019-04-14T17:09:58.738Z","updated":"2019-09-24T12:47:14.895Z","comments":true,"path":"2019/04/15/A21-PEP8/","link":"","permalink":"https://www.itrhx.com/2019/04/15/A21-PEP8/","excerpt":"之前一直用 Python IDLE 写代码,最近换成 PyCharm 写代码总是会出现波浪号,这才了解到 Python 的 PEP8 代码规范,所以将常见的 PEP8 代码规范问题和解决方法记录一下,学习一下,遇到了再持续更新,养成良好的习惯,编写规范的代码!","text":"之前一直用 Python IDLE 写代码,最近换成 PyCharm 写代码总是会出现波浪号,这才了解到 Python 的 PEP8 代码规范,所以将常见的 PEP8 代码规范问题和解决方法记录一下,学习一下,遇到了再持续更新,养成良好的习惯,编写规范的代码! PEP 8: no newline at end of file解决方法:代码末尾需要另起一行,光标移到最后回车即可 PEP 8: indentation is not a multiple of four解决方法:缩进不是4的倍数,检查缩进 PEP 8: over-indented解决方法:过度缩进,检查缩进 PEP 8: missing whitespace after’,’解决方法:逗号后面少了空格,添加空格即可,类似还有分号或者冒号后面少了空格 PEP 8: multiple imports on one line解决方法:不要在一句 import 中引用多个库,举例:import socket, urllib.error最好写成:import socket import urllib.error PEP 8: blank line at end of line解决方法:代码末尾行多了空格,删除空格即可 PEP 8: at least two spaces before inline comment解决方法:代码与注释之间至少要有两个空格 PEP 8: block comment should start with ‘#’解决方法:注释要以#加一个空格开始 PEP 8: inline comment should start with ‘#’解决方法:注释要以#加一个空格开始 PEP 8: module level import not at top of file解决方法:import不在文件的最上面,可能之前还有其它代码 PEP 8: expected 2 blank lines,found 0解决方法:需要两条空白行,添加两个空白行即可 PEP 8: function name should be lowercase解决方法:函数名改成小写即可 PEP 8: missing whitespace around operator解决方法:操作符(’=’、’>’、’<’等)前后缺少空格,加上即可 PEP 8: unexpected spaces around keyword / parameter equals解决方法:关键字/参数等号周围出现意外空格,去掉空格即可 PEP 8: multiple statements on one line (colon)解决方法:多行语句写到一行了,比如:if x == 2: print('OK')要分成两行写 PEP 8: line too long (82 > 79 characters)解决方法:超过了每行的最大长度限制79 如果想要选择性忽略PEP8代码风格的警告信息可以使用以下方法:(养成良好的习惯,编写规范的代码!不推荐忽略!) ①将鼠标移到出现警告信息的地方,按 alt+Enter,选择忽略(Ignore)这个错误即可:②依次选择 File - Settings - Editor - Inspections,在 Python下找到 PEP8 coding style violation 选项,在右下角的 Ignore errors 里点击加号可以添加需要忽略的警告信息ID(ID信息见后面附录),例如想要忽略indentation contains mixed spaces and tabs这个警告,只需要添加其ID:E101 即可附录:全部警告信息以及对应的ID,官方地址:https://pep8.readthedocs.io/en/latest/intro.html#error-codes code sample message E1 Indentation E101 indentation contains mixed spaces and tabs E111 indentation is not a multiple of four E112 expected an indented block E113 unexpected indentation E114 indentation is not a multiple of four (comment) E115 expected an indented block (comment) E116 unexpected indentation (comment) E117 over-indented E121 (*^) continuation line under-indented for hanging indent E122 (^) continuation line missing indentation or outdented E123 (*) closing bracket does not match indentation of opening bracket’s line E124 (^) closing bracket does not match visual indentation E125 (^) continuation line with same indent as next logical line E126 (*^) continuation line over-indented for hanging indent E127 (^) continuation line over-indented for visual indent E128 (^) continuation line under-indented for visual indent E129 (^) visually indented line with same indent as next logical line E131 (^) continuation line unaligned for hanging indent E133 (*) closing bracket is missing indentation E2 Whitespace E201 whitespace after ‘(‘ E202 whitespace before ‘)’ E203 whitespace before ‘:’ E211 whitespace before ‘(‘ E221 multiple spaces before operator E222 multiple spaces after operator E223 tab before operator E224 tab after operator E225 missing whitespace around operator E226 (*) missing whitespace around arithmetic operator E227 missing whitespace around bitwise or shift operator E228 missing whitespace around modulo operator E231 missing whitespace after ‘,’, ‘;’, or ‘:’ E241 (*) multiple spaces after ‘,’ E242 (*) tab after ‘,’ E251 unexpected spaces around keyword / parameter equals E261 at least two spaces before inline comment E262 inline comment should start with ‘# ‘ E265 block comment should start with ‘# ‘ E266 too many leading ‘#’ for block comment E271 multiple spaces after keyword E272 multiple spaces before keyword E273 tab after keyword E274 tab before keyword E275 missing whitespace after keyword E3 Blank line E301 expected 1 blank line, found 0 E302 expected 2 blank lines, found 0 E303 too many blank lines (3) E304 blank lines found after function decorator E305 expected 2 blank lines after end of function or class E306 expected 1 blank line before a nested definition E4 Import E401 multiple imports on one line E402 module level import not at top of file E5 Line length E501 (^) line too long (82 > 79 characters) E502 the backslash is redundant between brackets E7 Statement E701 multiple statements on one line (colon) E702 multiple statements on one line (semicolon) E703 statement ends with a semicolon E704 (*) multiple statements on one line (def) E711 (^) comparison to None should be ‘if cond is None:’ E712 (^) comparison to True should be ‘if cond is True:’ or ‘if cond:’ E713 test for membership should be ‘not in’ E714 test for object identity should be ‘is not’ E721 (^) do not compare types, use ‘isinstance()’ E722 do not use bare except, specify exception instead E731 do not assign a lambda expression, use a def E741 do not use variables named ‘l’, ‘O’, or ‘I’ E742 do not define classes named ‘l’, ‘O’, or ‘I’ E743 do not define functions named ‘l’, ‘O’, or ‘I’ E9 Runtime E901 SyntaxError or IndentationError E902 IOError W1 Indentation warning W191 indentation contains tabs W2 Whitespace warning W291 trailing whitespace W292 no newline at end of file W293 blank line contains whitespace W3 Blank line warning W391 blank line at end of file W5 Line break warning W503 (*) line break before binary operator W504 (*) line break after binary operator W505 (*^) doc line too long (82 > 79 characters) W6 Deprecation warning W601 .has_key() is deprecated, use ‘in’ W602 deprecated form of raising exception W603 ‘<>’ is deprecated, use ‘!=’ W604 backticks are deprecated, use ‘repr()’ W605 invalid escape sequence ‘x’ W606 ‘async’ and ‘await’ are reserved keywords starting with Python 3.7","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://www.itrhx.com/tags/Python/"},{"name":"PEP8","slug":"PEP8","permalink":"https://www.itrhx.com/tags/PEP8/"}]},{"title":"VMware Pro 15 安装 Deepin15.9 国产操作系统","slug":"A20-install-deepin15.9","date":"2019-04-14T12:53:34.310Z","updated":"2019-09-09T14:00:05.684Z","comments":true,"path":"2019/04/14/A20-install-deepin15.9/","link":"","permalink":"https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/","excerpt":"","text":"Deepin是由武汉深之度科技有限公司开发的Linux发行版,个人认为其界面设计非常美观,而且作为国产操作系统,值得我们去体验和支持! 1.下载安装 VMware Workstation Pro 15 进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装 2.下载 Deepin15.9 系统 进入 deepin 官网,下载最新版 deepin 系统镜像 3.在 VMware 中创建虚拟机打开安装好的 VMware Workstation Pro 15,选择创建新的虚拟机在新建虚拟机向导中选择自定义(高级):默认直接下一步,直到出现下图,再选择稍后安装操作系统:选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本:更改虚拟机名称及存放位置:接下来为虚拟机指定处理器数量、分配内存(太大了可能会导致卡顿,太小了也不好,推荐内存大小即可)一直选择默认即可,选择磁盘时,选择创建新虚拟磁盘:选择将虚拟磁盘储存为单个文件:默认下一步:点击完成:此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Deepin,单击 Deepin,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Deepin 15.9 镜像文件,点击“确定” 4.在虚拟机上安装 Deepin 系统单击 Deepin,选择“开启此虚拟机”接下来就是选择语言、创建用户、选择时区、指定磁盘等过程:安装完成后:可以看见界面还是相当美观的,系统也自带了深度的一些软件,比如深度录屏,深度录音,深度影院,深度计算器等等的一些小工具,作为国产操作系统,个人觉得已经非常优秀了,值得去体验!","categories":[{"name":"Linux","slug":"Linux","permalink":"https://www.itrhx.com/categories/Linux/"}],"tags":[{"name":"VMware","slug":"VMware","permalink":"https://www.itrhx.com/tags/VMware/"},{"name":"Deepin","slug":"Deepin","permalink":"https://www.itrhx.com/tags/Deepin/"}]},{"title":"Windows 系统中 Pygame 的安装","slug":"A19-install-pygame","date":"2019-03-10T14:34:09.591Z","updated":"2019-09-24T12:47:07.382Z","comments":true,"path":"2019/03/10/A19-install-pygame/","link":"","permalink":"https://www.itrhx.com/2019/03/10/A19-install-pygame/","excerpt":"","text":"Pygame是跨平台Python模块,专为电子游戏设计,可用于管理图形、动画乃至声音,建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚,通过使用Pygame来处理在屏幕上绘制图像等任务,你不用考虑众多繁琐而艰难的编码工作,而是将重点放在程序的高级逻辑上。 你可以从以下三个地址查找与你运行的Python版本相匹配的Windows安装程序: https://bitbucket.org/pygame/pygame/downloads/ (Pygame项目托管在代码分享网站Bitbucket中) http://www.pygame.org/download.shtml (Pygame官网) https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame (如果以上两个地址找不到合适的安装程序,推荐去这个) 如果下载的是.exe文件,直接运行它,如果下载的是.whl文件,就需要打开命令窗口,切换到该文件所在的目录,使用pip来运行它: 首先检查电脑是否安装了pip,打开终端窗口,执行如下命令:1>python -m pip --version 如果输出版本信息则已安装:1>pip 18.1 from E:\\Python\\lib\\site-packages\\pip (python 3.6) 否则请安装pip,访问 https://bootstrap.pypa.io/get-pip.py ,如果出现对话框请直接保存文件,如果出现的是get-pip.py的源代码,则需要新建一个get-pip.py文件,将该代码复制粘贴到其中,使用下面的命令运行get-pip.py:1>python get-pip.py 安装完成后可再次使用python -m pip --version命令检查是否成功安装了pip,成功安装pip后,使用以下命令来安装Pygame:(注意要先cd到你下载的文件的目录)1>python -m pip install --user 下载的.whl文件名 出现以下信息则表示安装成功:1>Successfully installed 你安装的Pygame版本 比如我的Python版本是3.6.5,64位的,则需要下载pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl,该文件保存到了桌面,使用下面的命令安装Pygame:12345678C:\\Users\\Lenovo>cd desktopC:\\Users\\Lenovo\\Desktop>python -m pip install --user pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whlProcessing c:\\users\\lenovo\\desktop\\pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whlInstalling collected packages: pygameSuccessfully installed pygame‑1.9.4C:\\Users\\Lenovo>Desktop> 检查是否成功安装Pygame:在Python的IDLE里输入import pygame,如果不报错,则安装成功,再输入pygame.ver就能看到版本号: ) 可能出现的问题:报错:xxxxxxxxxxxxxxxxxxxxxx.whl is not a supported wheel on this platform.原因:Python版本与Pygame版本不对应解决方法:Pygame文件名中的cp**表示Python对应的版本,另外并不是你电脑64位则下载64位,要看你安装的Python是否为64位,注意下载对应的版本! 报错:You are using pip version x.x.x, however version x.x.x is available.You should consider upgrading via the 'python -m pip install --upgrade pip' command.原因:版本需要更新解决方法:输入python -m pip install --upgrade pip命令进行更新即可 Pygame安装完成后我们就可以使用Python来开发游戏啦!","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"Pygame","slug":"Pygame","permalink":"https://www.itrhx.com/tags/Pygame/"},{"name":"Python","slug":"Python","permalink":"https://www.itrhx.com/tags/Python/"}]},{"title":"免费CDN:jsDelivr + Github","slug":"A18-free-cdn","date":"2019-02-10T14:30:17.903Z","updated":"2019-09-09T14:09:28.003Z","comments":true,"path":"2019/02/10/A18-free-cdn/","link":"","permalink":"https://www.itrhx.com/2019/02/10/A18-free-cdn/","excerpt":"","text":"本文有参考《jsDelivr+github使用教程,免费好用的cdn》—— By hojun CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。——百度百科 放在Github的资源在国内加载速度比较慢,因此需要使用CDN加速来优化网站打开速度,jsDelivr + Github便是免费且好用的CDN,非常适合博客网站使用。 1、新建Github仓库 2、克隆Github仓库到本地 点击 Clone or download,一键复制仓库地址 在本地目录右键 Git Bash Here,执行以下命令: 1git clone 一键复制的仓库地址 3、上传资源 复制需要上传的资源到本地git仓库(注:jsDelivr不支持加载超过20M的资源),在本地git仓库目录下右键 Git Bash Here,执行以下命令:1234git status //查看状态git add . //添加所有文件到暂存区git commit -m '第一次提交' //把文件提交到仓库git push //推送至远程仓库 4、发布仓库 点击release发布 自定义发布版本号 5、通过jsDelivr引用资源 使用方法:https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径例如:https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@1.0/images/trhx.png    https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.1/css/style.css    https://cdn.jsdelivr.net/gh/moezx/cdn@3.1.3//The%20Pet%20Girl%20of%20Sakurasou.mp4 注意:版本号不是必需的,是为了区分新旧资源,如果不使用版本号,将会直接引用最新资源,除此之外还可以使用某个范围内的版本,查看所有资源等,具体使用方法如下: // 加载任何Github发布、提交或分支https://cdn.jsdelivr.net/gh/user/repo@version/file // 加载 jQuery v3.2.1https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/dist/jquery.min.js // 使用版本范围而不是特定版本https://cdn.jsdelivr.net/gh/jquery/jquery@3.2/dist/jquery.min.jshttps://cdn.jsdelivr.net/gh/jquery/jquery@3/dist/jquery.min.js // 完全省略该版本以获取最新版本https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js // 将“.min”添加到任何JS/CSS文件中以获取缩小版本,如果不存在,将为会自动生成https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/src/core.min.js // 在末尾添加 / 以获取资源目录列表https://cdn.jsdelivr.net/gh/jquery/jquery/","categories":[{"name":"CDN","slug":"CDN","permalink":"https://www.itrhx.com/categories/CDN/"}],"tags":[{"name":"jsDelivr","slug":"jsDelivr","permalink":"https://www.itrhx.com/tags/jsDelivr/"},{"name":"CDN","slug":"CDN","permalink":"https://www.itrhx.com/tags/CDN/"}]},{"title":"新年快乐!","slug":"A17-happy-new-year","date":"2019-02-04T19:09:51.832Z","updated":"2019-09-09T14:11:08.281Z","comments":true,"path":"2019/02/05/A17-happy-new-year/","link":"","permalink":"https://www.itrhx.com/2019/02/05/A17-happy-new-year/","excerpt":"","text":"C printf("2019,祝大家"); C++ cout<<"一帆风顺"; C# System.Console.WriteLine("二龙腾飞") VB Msg("三羊开泰") VC MessageBox("四季平安"); Java System.out.println("五福临门"); JavaScript alert("六六大顺") PHP echo "七星高照"; Python print("八方来财") Html <br/>九运当头<br/> Objectivec NSLog(@"十全十美"); QBasic Print "阖家幸福" Asp Response.Write "心想事成" Ruby puts "财源广进" VBScript MsgBox "幸福安康" XML <TextView android:text="大展宏图" /> LUA print("学业有成") Delphi ShowMessage('万事如意'); shell echo 步步高升 perl print '鸿案齐眉' LISP (format t "身体健康~%") powerBuilder messagebox("龙马精神") COBOL DISPLAY '笑口常开' aswing JOptionPane.showMessageDialog("happy","好运连连") Android Toast.makeText(getApplicationContext(),"年年有余",Toast.LENGTH_SHORT).show() flex Alert.show("大吉大利"); Foxpro ?[家庭幸福!] iapp tw("瑞气盈门") DOS批处理 echo 鹏程万里 易语言 调试输出(“万事亨通”) Clojure (println "年年有今昔") verilog/systemverilog/e $display("岁岁有今朝") as trace("祝大家新年快乐!");","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/tags/BLOG/"}]},{"title":"一台电脑使用两个/多个GitHub账号部署两个/多个Hexo博客","slug":"A16-deploy-two-or-more-hexo-blogs","date":"2019-01-18T13:23:24.345Z","updated":"2020-02-07T07:30:20.946Z","comments":true,"path":"2019/01/18/A16-deploy-two-or-more-hexo-blogs/","link":"","permalink":"https://www.itrhx.com/2019/01/18/A16-deploy-two-or-more-hexo-blogs/","excerpt":"由于个人原因需要在一台电脑上部署两个Hexo博客,本来以为挺简单,没想到问题重重,首先是一个GitHub账号只能搭建一个Hexo博客,因此就需要使用其他GitHub账号;其次是一台电脑绑定两个GitHub账号,则需要两对公钥,在处理第二个问题时遇到的问题比较多,因为对这方面一窍不通,还是小白,所以折腾了一下午才解决,网上好多教程我都看不懂,觉得不(自)够(己)详(太)细(笨),因此详细记录一下","text":"由于个人原因需要在一台电脑上部署两个Hexo博客,本来以为挺简单,没想到问题重重,首先是一个GitHub账号只能搭建一个Hexo博客,因此就需要使用其他GitHub账号;其次是一台电脑绑定两个GitHub账号,则需要两对公钥,在处理第二个问题时遇到的问题比较多,因为对这方面一窍不通,还是小白,所以折腾了一下午才解决,网上好多教程我都看不懂,觉得不(自)够(己)详(太)细(笨),因此详细记录一下 原理分析: SSH的公钥是GitHub作为本地仓库和远程仓库连接的唯一标识,一个公钥只能对应一个GitHub账户,如果将一个相同的公钥上传到不同的GitHub账户,GitHub则无法做出辨识,进而导致错误 一台电脑,可以生成多对公私钥,可以通过配置,将不同的公钥上传到不同的GitHub账号,那么就不存在单个公钥绑定多个GitHub账号的情况存在了 相关问题报错: 同一台电脑部署第二个Hexo博客执行hexo g -d时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx. 添加新的 SSH 密钥 到 SSH agent 执行ssh-add xxx时报错:Could not open a connection to your authentication agent. 单独设置用户名/邮箱时报错:fatal: not in a git directory 以下是详细过程:前提:假设你的第二个博客相关配置操作已经顺利完成,但使用hexo g -d命令部署到 GitHub 上时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx. - 查看当前密钥首先我们打开终端输入ls ~/.ssh/可以查看当前已有的密钥,显示id_rsa 与 id_rsa_pub说明已经有一对密钥 - 创建新的密钥首先使用以下命令进入 SSH根目录下:1cd ~/.ssh/ 方法一直接使用以下命令创建新密钥,然后两次回车即可:1ssh-keygen -t rsa -f ~/.ssh/这里是新密钥名称 -C \"这里是你的邮箱\" 注意区别新密钥名称和旧密钥名称,不要相同!!! 方法二使用下面命令行创建新密钥:1ssh-keygen -t rsa -C \"这里是你的邮箱\" 回车后会出现:12Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/you/.ssh/id_rsa): 注意此时需要你输入新密钥的名称,同样要注意区别新密钥名称和旧密钥名称,不要相同!!!之后再两次回车,新密钥创建完毕! - 配置config查看你的.ssh/根路径下, 有没有config文件,( 比如我的路径为C:\\Users\\Lenovo.ssh)没有则使用以下命令创建一个config文件:1touch config 用记事本或者其他工具打开config文件(注意config文件是没有任何后缀名的),写入以下配置: 1234567891011#第一个账号,默认使用的账号,不用做任何更改Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa #第二个新账号,#\"xxxxxx\"为前缀名,可以任意设置,要记住,后面需要用到Host xxxxxx.github.com HostName github.com User git IdentityFile ~/.ssh/这里是你创建的新密钥的名称 - 设置新GitHub账户SSH key输入以下命令复制你创建的公钥:1clip < ~/.ssh/这里是你创建的新密钥的名称.pub 也可以直接在.ssh目录下找到你创建的新的公钥,文件名为新密钥的名称.pub,(比如我的是trhx_rsa.pub),用记事本打开,复制里面的内容,然后打开你的新GitHub账号主页,依次进入Settings —> SSH and GPG keys —> New SSH key,将刚复制的内容粘贴到Key那里,Title可以随便填,点击Add Key保存。 - 清空本地的 SSH 缓存,添加新的 SSH 密钥 到 SSH agent中使用命令cd ~/.sshcd到.ssh根目录下,依次执行以下命令: 123ssh-add -Dssh-add xxxxxx #旧密钥名称,一般是id_rsassh-add xxxxxx #新创建的密钥名称 如果执行以上命令出现错误:Could not open a connection to your authentication agent.,那么就需要先执行ssh-agent bash,再执行以上命令 - 验证配置是否成功依次执行以下命令,第一个为默认ssh_key验证;第二个为新的ssh_key验证,其中“xxxxxx”为你先前在config文件中的命名12ssh -T git@github.comssh -T git@xxxxxxx.github.com 依次显示以下信息, 则说明配置成功:1Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access. - 取消全局用户名/邮箱配置,单独设置用户名/邮箱执行如下命令,取消全局用户名和邮箱配置(如果已经设置了全局的话): 12git config --global --unset user.namegit config --global --unset user.email 分别进入你的两个Hexo博客.git目录下执行以下命令单独设置用户名/邮箱:12git config user.name \"这里是用户名\"git config user.email \"这里是你的邮箱\" 如果此时报错:fatal: not in a git directory,说明你没有进入.git目录下,具体路径:\\Hexo\\.deploy_git\\.git,.git目录是隐藏的,需要你设置隐藏目录可见 执行以下命令可以查看设置是否成功1git config --list - hexo 配置文件修改git地址打开你的第二个博客Hexo目录下的_config.yml文件,找到deploy关键字,写入以下配置并保存:1234deploy: type: git repository: git@xxxxxx.github.com:你的用户名/你的用户名.github.io.git branch: master 比如我的配置:1234deploy: type: git repository: git@love109.github.com:love109/love109.github.io.git branch: master 大功告成,再次执行hexo g -d就能成功将新的博客部署到 Github 上了","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"Github","slug":"Github","permalink":"https://www.itrhx.com/tags/Github/"}]},{"title":"Python3 基础学习笔记 C09","slug":"A15-Python3-basic-C09","date":"2018-11-15T16:46:13.649Z","updated":"2019-09-24T12:45:51.693Z","comments":true,"path":"2018/11/16/A15-Python3-basic-C09/","link":"","permalink":"https://www.itrhx.com/2018/11/16/A15-Python3-basic-C09/","excerpt":"Python3 基础学习笔记第九章 —— 【文件和异常】","text":"Python3 基础学习笔记第九章 —— 【文件和异常】 - 9.1 从文件中读取数据 - 9.1.1 读取整个文件 有一个文件,包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:12345Circumference rate.txt----------3.1415926535 8979323846 2643383279 以下两个程序将打开并读取这个文件,再将其内容显示到屏幕上:12345#file_reader.pywith open('Circumference rate.txt') as file_object: contents = file_object.read() print(contents) 12345#file_reader2.pycontents = open ('Circumference rate.txt')print(contents.read())contents.close() 函数open()接受一个参数:要打开的文件的名称,Python在当前执行的文件所在的目录中查找指定的文件;关键字with在不再需要访问文件后将其关闭;也可以调用open()和close()来打开和关闭文件,如果使用这种方法,当程序存在bug时,close()语句未执行,文件将不会被关闭;方法read()将读取这个文件的全部内容,并将其作为一个长长的字符串储存在变量contents中,通过打印contents的值,就可以将这个文本文件的全部内容打印出来:1233.1415926535 8979323846 2643383279 输出结果末尾有一空行,这是因为read()到达末尾时返回一个空字符串,而将这个空字符串显示出来就是一个空行,如果要删除末尾的空行,可在print语句中使用rstrip():12345#file_reader.pywith open('Circumference rate.txt') as file_object: contents = file_object.read() print(contents.rstrip()) 输出结果如下:1233.1415926535 8979323846 2643383279 - 9.1.2 文件路径 相对文件路径:假定程序文件位于python_work文件夹中,程序文件操作的文本文件位于python_work文件夹的子文件夹text_files中,此时可以使用相对文件路径来打开该文本文件,相对文件路径让Python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的 在Linux和OS X中,相对路径类似于如下:1with open('text_files/filename.txt') as file_object: 在Windows系统中,文件路径中使用反斜杠(\\)而不是斜杠(/):1with open('text_files\\filename.txt') as file_object: 绝对文件路径:不用关心当前运行的程序储存在什么地方,直接将文件在计算机中的准确位置告诉Python,这称为绝对文件路径,绝对路径通常比相对路径更长,因此将其储存在一个变量中,再将变量传递给open()会有所帮助 在Linux和OS X中,绝对路径类似于如下:12file_path = '/home/ehmatthes/other_files/text_files/filename.txt'with open(file_path) as file_object: 在Windows系统中,绝对路径类似于如下:12file_path = 'C:\\Users\\ehmatthes\\other_files\\text_files\\filename.txt'with open(file_path) as file_object: - 9.1.3 逐行读取 要以每次一行的方式检查文件,可对文件对象使用for循环:123456#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: for line in file_object: print(line) 在文件中每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,一个来自print语句,输出结果如下:123453.1415926535 8979323846 2643383279 要消除这些多余的空白行,可以使用rstrip():123456#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: for line in file_object: print(line.rstrip()) 输出结果如下:1233.1415926535 8979323846 2643383279 - 9.1.4 创建一个包含文件各行内容的列表 使用关键字with时,open()返回的文件对象只在with代码块内可用,如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行储存在一个列表当中,并在with代码块外使用该列表:12345678#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.rstrip()) 输出结果与文件内容完全一致 - 9.1.5 使用文件的内容 创建一个字符串,它包含文件中储存的所有数字,且没有任何空格:123456789101112#pi_string.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines()pi_string = ''for line in lines: pi_string += line.rstrip() print(pi_string)print(len(pi_string)) 打印该字符串以及其长度:123.1415926535 8979323846 264338327936 由于原文件每行左边都有空格,我们可以使用strip()而不是rstrip()来删除它:123456789101112#pi_string.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines()pi_string = ''for line in lines: pi_string += line.strip() print(pi_string)print(len(pi_string)) 输出结果如下:123.14159265358979323846264338327932 Python中有三个去除头尾字符、空白符的函数,它们依次为: strip:用来去除头尾字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格) lstrip:用来去除开头字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格) rstrip:用来去除结尾字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格)注意:这些函数都只会删除头和尾的字符,中间的不会删除。用法分别为:string.strip([chars])string.lstrip([chars])string.rstrip([chars])参数chars是可选的,当chars为空,默认删除string头尾的空白符(包括\\n、\\r、\\t、’ ‘)当chars不为空时,函数会被chars解成一个个的字符,然后将这些字符去掉它返回的是去除头尾字符(或空白符)的string副本,string本身不会发生改变 - 9.2 写入文件 将一条简单的消息储存到文件中:12345#write_message.pyfilename = 'programming.txt'with open(filename,'w') as file_object: file_object.write(\"I love programming!\") 调用open()时提供了两个实参,第一个实参也是要打开文件的名称,第二个实参(’w’)告诉Python,我们要以写入模式打开这个文件,打开文件时,可指定读取模式(’r’)、写入模式(’w’)、附加模式(’a’)或者让我们能够读取和写入文件的模式(’r+’),如果省略模式实参,则默认以只读模式打开文件 附表:Python读写文件各种模式区别 模式 可做操作 若文件不存在 是否覆盖 r 打开一个文件用于只读 报错 - rb 以二进制格式打开一个文件用于只读 报错 - r+ 打开一个文件用于读和写 报错 是 rb+ 以二进制格式打开一个文件用于读和写 报错 是 w 打开一个文件用于只写 创建 是 wb 以二进制格式打开一个文件只用于只写 创建 是 w+ 打开一个文件用于读和写 创建 是 wb+ 以二进制格式打开一个文件用于读和写 创建 是 a 打开一个文件用于追加 创建 否,追加写 ab 以二进制格式打开一个文件用于追加 创建 否,追加写 a+ 打开一个文件用于读和写 创建 否,追加写 ab+ 以二进制格式打开一个文件用于追加 创建 否,追加写 - 9.3 使用 try-except 代码块处理异常 当我们尝试将一个数字除以0时,会发生ZeroDivisionError异常:12345>>> print(5/0)Traceback (most recent call last): File \"<pyshell#0>\", line 1, in <module> print(5/0)ZeroDivisionError: division by zero 此时我们可以编写一个try-except代码块来处理该异常:1234try: print(5/0)except ZeroDivisionError: print(\"You can't divide by zero!\") 当我们运行该程序时,会出现提示:1You can't divide by zero! 在try-except代码块中加入else,编写一个只执行除法运算的简单计算器:12345678910111213141516print(\"Give me two numbers,and I'll divide them.\")print(\"Enter 'q' to quit.\")while True: first_number = input(\"\\nFirst number:\") if first_number == 'q': break second_number = input(\"\\nSecond number:\") if second_number == 'q': break try: answer = int(first_number)/int(second_number) except ZeroDivisionError: print(\"You can't divide by 0!\") else: print(answer) 运行程序:1234567891011121314Give me two numbers,and I'll divide them.Enter 'q' to quit.First number:45Second number:0You can't divide by 0!First number:36Second number:84.5First number:q 若不加入try-except代码块,我们在输入0时,程序就会出现异常而崩溃,而try-except代码块很好的解决了这种问题,而且还起到了提示的作用,同样的,try-except代码块也可以处理其他异常,如FileNotFoundError等 - 9.4 储存数据 - 9.4.1 使用 json.dump() 和 json.load() 模块json能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据;编写一个储存一组数字的简短程序,再编写一个将这些数字读取到内存中的程序,第一个程序将使用 json.dump()来储存这组数据,而第二个程序将使用 json.load()。函数 json.dump()接受两个实参:要储存的数据以及可用于储存数据的文件对象:123456789#number_writer.pyimport jsonnumbers = [2,3,5,7,11,13]filename = 'numbers.json'with open(filename,'w') as f_obj: json.dump(numbers,f_obj) 先导入模块json,再创建一个数字列表, 通常用文件扩展名.json来指出文件储存的数据为JSON格式,然后以写入模式打开该文件,使用函数json.dump()将数字列表储存到文件numbers.json中,打开该文件,数据的储存格式与Python一样:1[2, 3, 5, 7, 11, 13] 再编写一个程序,使用json.load()将这个列表读取到内存中:12345678#number_reader.pyimport jsonfilename = 'numbers.json'with open(filename) as f_obj: numbers = json.load(f_obj)print(numbers) 输出结果与number_writer.py中创建的数字列表相同:1[2, 3, 5, 7, 11, 13] 进阶:在同一个程序中使用 json.dump() 和 json.load():创建文件username.json储存用户名,从该文件中获取用户名,如果这个文件不存在,就在except代码块中提示用户输入用户名,并将其储存在username.json中:1234567891011121314151617#remember_me.pyimport json#如果以前储存了用户名,就加载它#否则就提示用户输入用户名并储存它filename = 'numbers.json'try: with open(filename) as f_obj: username = json.load(f_obj)except FileNotFoundError: username = input(\"What's your name?\") with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\")else: print(\"Welcome back, \" + username + \"!\") 以前没有储存用户名,第一次运行程序:12What's your name?TRHXWe'll remember you when you come back, TRHX! 再次运行程序:1Welcome back, TRHX! - 9.4.2 重构 代码能够正确运行,但可以做进一步的改进——将代码划分为一系列完成具体工作的函数,这样的过程称为重构,重构让代码更清晰、更易于理解、更容易扩展重构remember_me.py,将大部分逻辑放到一个或者多个函数中:12345678910111213141516171819#remember_me.pyimport jsondef greet_user(): #问候用户,并指出其名字 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input(\"What's your name?\") with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\") else: print(\"Welcome back, \" + username + \"!\")greet_user() 重构greet_user(),让它不执行这么多任务——将获取储存的用户名的代码移到另一个函数中:12345678910111213141516171819202122232425262728#remember_me.pyimport jsondef get_stored_username(): #如果储存了用户名,就获取它 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return usernamedef greet_user(): #问候用户,并指出其名字 username = get_stored_username() if username: print(\"Welcome back, \" + username + \"!\") else: username = input(\"What's your name?\") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\") greet_user() 将greet_user()中的另一个代码块提取出来:将没有储存用户名时提示用户输入的代码放在一个独立的函数中:12345678910111213141516171819202122232425262728293031323334#remember_me.pyimport jsondef get_stored_username(): #如果储存了用户名,就获取它 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return usernamedef get_new_username(): #提示输入用户名 username = input(\"What's your name?\") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) return username def greet_user(): #问候用户,并指出其名字 username = get_stored_username() if username: print(\"Welcome back, \" + username + \"!\") else: username = get_new_username() print(\"We'll remember you when you come back, \" + username + \"!\") greet_user() 最终版本实现了每个函数只负责单一而清晰的任务,我们在编写程序时也要像这样,要写出清晰而易于维护和扩展的代码","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"文件","slug":"文件","permalink":"https://www.itrhx.com/tags/文件/"},{"name":"异常","slug":"异常","permalink":"https://www.itrhx.com/tags/异常/"}]},{"title":"Python3 基础学习笔记 C08","slug":"A14-Python3-basic-C08","date":"2018-11-10T16:10:47.892Z","updated":"2019-09-24T12:45:48.188Z","comments":true,"path":"2018/11/11/A14-Python3-basic-C08/","link":"","permalink":"https://www.itrhx.com/2018/11/11/A14-Python3-basic-C08/","excerpt":"Python3 基础学习笔记第八章 —— 【类】","text":"Python3 基础学习笔记第八章 —— 【类】 - 8.1 创建类和使用类 创建一个表示小狗的简单类Dog,根据Dog类创建的每个实例都将储存名字和年龄,赋予每条小狗蹲下(sit())和打滚(roll_over())的能力: 1234567891011121314class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\") 方法init():类中的函数称为方法,本例中方法init()是一个特殊的方法,每当我们根据Dog类创建新实例时,Python都会自动运行它,在方法的名称中,开头和结尾各有两个下划线,这是一种约定,避免Python默认方法与普通方法发生名称冲突,例子中将方法init()定义成了包含三个形参:self、name和age,在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面,Python调用方法init()来创建Dog实例时,将自动传入实参self,每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法,我们创建Dog实例时,Python将调用Dog类的方法init(),我们将通过实参向Dog()传递名字和年龄;self会自动传递,因此我们不需要传递它,每当我们根据Dog类创建实例时,都只需要给最后两个形参(name和age)提供值;定义的两个变量都有前缀self,以self为前缀的变量都可以供类中的所有方法使用,还可以通过类的任何实例来访问这些变量。self.name = name 获取储存在形参name中的值,并将其储存到变量name中,然后该变量被关联到当前创建的实例。self.age = age 的作用与此类似,像这样可通过实例访问的变量称为属性;Dog还定义了另外两种方法:sit() 和 roll_over() ,由于这些方法不需要额外的信息,如名字和年龄,因此它们只有一个形参self 在Python 2.7中创建类时,需要在括号内包含单词object:12class ClassName(object): ---snip--- - 8.2 根据类创建实例访问属性:创建一个表示特定小狗的实例: 123456789101112131415161718class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)print(\"My dog's name is \" + my_dog.name.title() + \".\")print(\"My dog is \" + str(my_dog.age) + \" years old.\") 让Python创建一条名字为’willie’,年龄为6的小狗,Python使用实参’willie’和6调用Dog类中的方法init()。方法init()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age;在访问实例的属性时,可使用句点表示法,比如该例子中的 my_dog.name;最终程序输出结果如下: 12My dog's name is Willie.My dog is 6 years old. 调用方法:根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法:123456789101112131415161718class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)my_dog.sit()my_dog.roll_over() 输出结果如下:12Willie is now sitting.Willie rolled over! 创建多个实例:可按需求根据类创建任意数量的实例:12345678910111213141516171819202122232425class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)your_dog = Dog('lucy',8)print(\"My dog's name is \" + my_dog.name.title() + \".\")print(\"My dog is \" + str(my_dog.age) + \" years old.\")my_dog.sit()print(\"\\nYour dog's name is \" + your_dog.name.title() + \".\")print(\"Your dog is \" + str(your_dog.age) + \" years old.\")your_dog.roll_over() 输出结果如下:1234567My dog's name is Willie.My dog is 6 years old.Willie is now sitting.Your dog's name is Lucy.Your dog is 8 years old.Lucy rolled over! - 8.3 使用类和实例 创建一个表示汽车的类,其中储存了有关汽车的信息,还有一个汇总这些信息的方法:12345678910111213class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name()) 输出结果如下:12018 Audi A9 - 8.3.1 给属性指定默认值 类中的每个属性都必须有初始值,如果我们设置了默认值,就无需包含为它提供初始值的形参,下面为8.3的例子添加一个 odometer_reading 的属性,其初值是0,添加一个 odometer_reading() 方法,用于读取汽车的里程表: 123456789101112131415161718 class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\")my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.read_odomter() 输出结果如下:122018 Audi A9This car has 0 miles on it. - 8.3.2 修改属性的值 可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值) 直接修改属性的值:要修改属性的值,最简单的方法就是通过实例直接访问它,将8.3.1中的例子第7行代码 self.odometer_reading = 0 改为 self.odometer_reading = 66,输出结果如下:12 2018 Audi A9This car has 66 miles on it. 通过方法修改属性的值:1234567891011121314151617181920212223class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): self.odometer_reading = mileage my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(66)my_new_car.read_odomter() 对Car类所做的唯一修改就是在第17、18行添加了方法 update_odometer(),这个方法接受一个里程值,并将其储存到 self.odometer_reading 中,在倒数第二行,调用了 update_odometer(),并向它提供了一个实参(该实参对应于方法定义中的形参mileage),它将里程数设置为66,而方法 read_odomter() 打印该读数:122018 Audi A9This car has 66 miles on it. 可对方法 update_odometer() 进行扩展,使其能够在修改里程表读数时做一些额外的工作,添加一些逻辑,禁止任何人将里程表读数往回调:1234567891011121314151617181920212223242526class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 50 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(33)my_new_car.read_odomter() 修改 self.odometer_reading 的默认值为50,当我们再次尝试修改其值为33时,由于小于原来的里程,因此无法修改: 1232018 Audi A9You can't roll back an odometer!This car has 50 miles on it. 通过方法对属性的值进行递增:有时候需要将属性值递增到特定的量,而不是将其设置为全新的值,假设我们购买了一辆二手车,从购买到登记期间增加了100英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:123456789101112131415161718192021222324252627282930313233class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(6600)my_new_car.read_odomter()my_new_car.increment_odometer(100)my_new_car.read_odomter() 输出结果如下:1232018 Audi A9This car has 6600 miles on it.This car has 6700 miles on it. - 8.4 继承 编写类时,并非总是要从空白开始,如果要编写的类是另一个现成类的特殊版本,可使用继承,一个类继承另一个类时,它自动获得另一个类的所有属性和方法;原有的类称为父类,而新类称为子类,子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法;继承的通用语法大致如下: 12345678class ClassName1(object): def __init__(self,name1,name2,name3): --snip--class ClassName2(ClassName1): def __init__(self,name1,name2,name3): super().__init__(name1,name2,name3) --snip-- - 8.4.1 子类的方法init() 1234567891011121314151617181920 class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class ElectricCar(Car): #电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性 super().__init__(make,model,year)my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name()) 创建子类时,父类必须包含在当前文件中,且位于子类前面,定义了子类 ElectricCar,定义子类时,必须在括号内指定父类名称,方法 __init__()接受创建Car实例所需信息,super() 是一个特殊的函数,帮助Python将父类和子类关联起来,让Python调用 ElectricCar 的父类的方法 __init__(),让 ElectricCar 实例包含父类的所有属性,父类也称为超类(superclass),程序输出结果如下:12016 Tesla Model S - 8.4.2 Python 2.7 中的继承 在Python 2.7中,ElectricCar类的定义类似于下面这样:12345678class Car(object): def __init__(self,make,model,year): --snip--class ElectricCar(Car): def __init__(self,make,model,year): super(ElectricCar,self).__init__(make,model,year) --snip-- - 8.4.3 给子类定义属性和方法 让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法,下面添加一个电动车特有的属性(battery),以及一个描述该属性的方法: 1234567891011121314151617181920212223class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery_size = 80 def describe_battery(self): print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\")my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.describe_battery() 输出结果如下:122016 Tesla Model SThis car has a 80-KWh battery. - 8.4.4 重写父类的方法 要重写父类的方法,只需要在子类中定义一个与要重写的父类方法同名的方法即可,这样,Python将不会考虑这个父类的方法,而只关心在子类中定义的相应方法,假设Car类有一个名为 fill_gas_tank() 的方法,对于电动车来说毫无意义,因此可以重写它:12345class ElectricCar(Car): --snip-- def fill_gas_tank(self): print(\"This car doesn't need a gas tank!\") - 8.4.5 将实例用作属性 123456789101112131415161718192021222324252627282930class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\")class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery = Battery()my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.battery.describe_battery() 输出结果如下: 122016 Tesla Model SThis car has a 70-KWh battery. 看起来似乎做了多余的工作,但现在我们可以对电瓶添加更多的描述,而且不会导致 ElectricCar 类混乱不堪,下面再给Battery添加一个方法,使其能够根据电瓶容量报告汽车的续航里程:1234567891011121314151617181920212223242526272829303132333435363738394041class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message) class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery = Battery()my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.battery.describe_battery()my_new_car.battery.get_range() 输出结果如下:1232016 Tesla Model SThis car has a 70-KWh battery.This car can go approximately 240 miles on a full charge. - 8.5 导入类 Python允许将类储存在模块中,然后在主程序中导入所需的模块 - 8.5.1 导入单个类 12345678910111213141516171819202122232425262728293031#car.py#一个用于表示汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles 创建另一个文件——my_car.py,在其中导入Car类并创建其实例:123456789#my_car.pyfrom car import Carmy_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.odometer_reading = 23my_new_car.read_odometer() import语句让Python打开模块car,并导入其中的Car类,输出结果如下: 122018 Audi A9This car has 23 miles on it. - 8.5.2 在一个模块中储存多个类 将类Battery和ElectricCar都加入到模块car.py中: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#car.py#一组用于表示燃油汽车和电动汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odometer(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += milesclass Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message)class ElectricCar(Car): #模拟电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性,再初始化电动车特有的属性 super().__init__(make,model,year) self.battery = Battery() 新建一个my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动车:123456789#my_electric_car.pyfrom car import ElectricCarmy_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name())my_tesla.battery.describe_battery()my_tesla.battery.get_range() 输出结果如下:1232016 Tesla Model SThis car has a 70-KWh battery.This car can go approximately 240 miles on a full charge. - 8.5.3 从一个模块中导入多个类 可根据需要在程序文件中导入任意数量的类,假如我们要在同一个程序中创建普通汽车和电动汽车,就需要将类Car和ElectricCar类都导入,多个类之间用逗号进行分隔: 123456789 #my_car.pyfrom car import Car,ElectricCarmy_audi = Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 输出结果如下:122018 Audi A92016 Tesla Model S - 8.5.4 导入整个模块 导入整个模块后,需要使用句点表示法访问需要的类:123456789#my_car.pyimport carmy_audi = car.Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = car.ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 我们导入了整个car模块,需要使用语法 module_name.class_name 访问需要的类,程序输出结果与8.5.3一致:122018 Audi A92016 Tesla Model S - 8.5.5 导入模块中的所有类 要导入模块中的所有类,可使用以下语法:1from module_name import * 这种导入方法是不推荐的,没有明确指出你使用了模块中的哪些类,还可能引发名称方面的困惑,需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类 - 8.5.6 在一个模块中导入另一个模块 有时候需要将类分散到多个模块当中,以免模块太大,或者在同一个模块中储存不相关的类,将类储存在多个模块中时,一个模块中的类可能会依赖于另一个模块中的类,这种情况下,我们可以在前一个模块中导入必要的类,以下例子中,将Car类储存在一个模块当中,并将ElectricCar和Battery类储存在另一个模块当中,将第二个模块命名为electric_car.py,并将ElectricCar和Battery类复制到这个模块中:12345678910111213141516171819202122232425262728293031#electric_car.py#一组可用于表示电动汽车的类from car import Carclass Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message)class ElectricCar(Car): #模拟电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性,再初始化电动车特有的属性 super().__init__(make,model,year) self.battery = Battery() 12345678910111213141516171819202122232425262728293031#car.py#一个可用于表示汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odometer(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles 现在可以分别从每个模块中导入类:12345678910#my_car.pyfrom car import Carfrom electric_car import ElectricCarmy_audi = Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 输出结果如下:122018 Audi A92016 Tesla Model S - 8.6 Python标准库 Python标准库是一组模块,安装的Python都包含它,我们可以使用标准库中的任何函数和类,只需要在程序的开头包含一条简单的import语句,下面以模块collections中的一个类——OrderedDict(创建字典并记录其中的键-值对的添加顺序)为例:1234567891011121314#favorite_languages.pyfrom collections import OrderedDictfavorite_languages = OrderedDict()favorite_languages ['jen'] = 'python'favorite_languages ['sarah'] = 'c'favorite_languages ['edward'] = 'java'favorite_languages ['anly'] = 'python'for name,language in favorite_languages.items(): print(name.title() + \"'s favorite languages is \" + language.title() + \".\") 输出结果如下:1234Jen's favorite languages is Python.Sarah's favorite languages is C.Edward's favorite languages is Java.Anly's favorite languages is Python.","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"类","slug":"类","permalink":"https://www.itrhx.com/tags/类/"},{"name":"继承","slug":"继承","permalink":"https://www.itrhx.com/tags/继承/"}]},{"title":"Python3 基础学习笔记 C07","slug":"A13-Python3-basic-C07","date":"2018-11-03T14:21:47.735Z","updated":"2019-09-24T12:45:42.627Z","comments":true,"path":"2018/11/03/A13-Python3-basic-C07/","link":"","permalink":"https://www.itrhx.com/2018/11/03/A13-Python3-basic-C07/","excerpt":"Python3 基础学习笔记第七章 —— 【函数】","text":"Python3 基础学习笔记第七章 —— 【函数】 - 7.1 定义函数 一个简单的函数,命名为 example(),其中,关键字 def 来告诉Python我们要定义一个函数,这就是函数定义 123def example(): print(\"Hello world!\")example() 输出结果如下: 1Hello world! - 7.1.1 向函数传递信息 在函数定义 def example() 的括号中添加 username,可以让函数接受我们给 username 指定的任何值,在调用函数时给 username 指定一个值,调用 example() 时,可将一个名字传递给它: 123def example(username): print(\"Hello , \" + username + '!')example('TRHX') 输出结果如下: 1Hello , TRHX! - 7.1.2 实参和形参 在 7.1.1 的例子中,函数 example() 的定义中,变量 username 是一个形参——函数完成其工作所需的一项信息,在代码 example(‘TRHX’) 中,值’TRHX’是一个实参,实参是调用函数时传递给函数的信息,调用函数时,将要让函数使用的信息放在括号内。在 example(‘TRHX’) 中,将实参 ‘TRHX’ 传递给了函数 example,这个值被储存在形参 username 中 - 7.2 传递实参 鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参,这要求实参的顺序与形参的顺序相同;也可以使用关键字实参,其中每个实参都由变量和值组成;还可以使用列表和字典 - 7.2.1 位置实参 调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的方法是基于实参的顺序,这种关联方式被称为位置实参 1234def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet('hamster' , 'harry') 输出结果如下: 12I have a hamster.My hamster's name is Harry. 调用函数多次:我们可以根据需要调用函数任意次,要再描述一个宠物,只需要再次调用 123456describe_pet() 即可def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet('hamster' , 'harry')describe_pet('dog' , 'willi') 输出结果如下: 1234I have a hamster.Myhamster's name is Harry.I have a dog.My dog's name is Willi. - 7.2.2 关键字实参 关键字实参是传递给函数的名称-值对。直接在实参中将名称和值关联起来,不用考虑函数调用中的实参顺序 12345def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet(animal_type = 'hamster' , pet_name = 'harry')describe_pet(pet_name = 'willi' , animal_type = 'dog' ) 输出结果如下: 1234I have a hamster.Myhamster's name is Harry.I have a dog.My dog's name is Willi. - 7.2.3 默认值 编写函数时,可给每个形参指定默认值,在调用函数中给形参提供了实参时,Python将使用指定的实参值,否则将使用形参的默认值 1234def describe_pet(pet_name , animal_type = 'dog'): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet(pet_name = 'willi') 输出结果如下: 12I have a dog.My dog's name is Willi. 在这个函数定义中,修改了形参的排列顺序,由于给 animal_type 指定了默认值,无需通过实参来指定动物类型,因此在函数调用中只包含一个实参——宠物的名字,然而Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参,这就是需要将 pet_name 放在形参列表开头的原因所在 注意:使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的形参,这让Python依然能够准确地解读位置实参 - 7.3 返回值 函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值,函数返回的值被称为返回值,在函数中,可使用 return 语句将值返回到函数调用的代码行 - 7.3.1 返回简单值 12345def name(first_name , last_name): full_name = first_name + ' ' + last_name return full_name.title()student = name('jimi' , 'hendrix')print(student) 输出结果如下: 1Jimi Hendrix - 7.3.2 让实参变成可选的 对 7.3.1 的例子进行改进,扩展函数 name,使其还能够处理中间名: 12345def name(first_name , middle_name , last_name): full_name = first_name + ' ' + middle_name + ' ' + last_name return full_name.title()student = name('jimi' , 'lee' , 'hendrix')print(student) 输出结果如下: 1Jimi Lee Hendrix 然而,如果一个人没有中间名,那么在调用这个函数时就会出错,为了让中间名变成可选的,可以给实参 middle_name 指定一个默认值——空字符串,并在用户没有提供中间名时不使用这个实参,注意需要将 middle_name 移到形参列表的末尾: 12345678910def name(first_name , last_name , middle_name = ' '): if middle_name: full_name = first_name + ' ' + middle_name + ' ' + last_name else: full_name = first_name + ' ' + last_name return full_name.title()student = name('jimi' , 'hendrix')print(student)student = name('jimi' , 'hendrix' , 'lee' )print(student) 输出结果如下: 12Jimi HendrixJimi Lee Hendrix - 7.3.3 返回字典 函数可返回任何类型的值,包括列表和字典等较复杂的数据结构: 12345def name(first_name , last_name): full_name = {'first' : first_name , 'last' : last_name} return full_namestudent = name('jimi' , 'hendrix')print(student) 输出结果如下: 1{'first': 'jimi', 'last': 'hendrix'} - 7.3.4 结合使用函数和 while 循环 123456789101112131415def name(first_name , last_name): full_name = first_name + ' ' + last_name return full_namewhile True: print(\"\\nPlease input your name:\") print(\"(Enter 'exit' to quit)\") f_name = input(\"First_name:\") if f_name == 'exit': break l_name = input(\"Last_name:\") if l_name == 'exit': break student = name(f_name , l_name) print(student) print(\"Hello, \" + student.title() + \"!\") 运行程序: 1234567891011Please input your name:(Enter 'exit' to quit)First_name:jimiLast_name:hendrixjimi hendrixHello, Jimi Hendrix!Please input your name:(Enter 'exit' to quit)First_name:exit - 7.4 传递列表 123456def users(names): for name in names: message = \"Hello, \" + name.title() + \"!\" print(message)usernames = ['hannah' , 'tony' , 'margot']users(usernames) 输出结果如下: 123Hello, Hannah!Hello, Tony!Hello, Margot! - 7.4.1 在函数中修改列表 将列表传递给函数后,函数就可以对其进行修改,在函数中对这个列表所做的任何修改都是永久性的 #首先创造一个列表,其中包含一些要打印的设计 12345678910111213141516unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']completed_models = []#模拟打印每个设计,直到没有未打印的设计为止#打印每个设计后,都将其移到列表completed_models中while unprinted_designs: current_design = unprinted_designs.pop() #模拟根据设计制作3D打印模型的过程 print(\"Printing model: \" + current_design) completed_models.append(current_design) #显示打印好的所有模型print(\"\\nThe following models have been printed: \")for completed_model in completed_models: print(completed_model) 输出结果如下: 12345678Printing model: dodecahedronPrinting model: robot pendanntPrinting model: iphone caseThe following models have been printed: dodecahedronrobot pendanntiphone case 编写两个函数重新组织这些代码,每一个函数都做一件具体的工作,输出结果与原程序相同: 123456789101112131415161718192021def print_models(unprinted_designs , completed_models):#模拟打印每个设计,直到没有未打印的设计为止#打印每个设计后,都将其移到列表completed_models中 while unprinted_designs: current_design = unprinted_designs.pop() #模拟根据设计制作3D打印模型的过程 print(\"Printing model: \" + current_design) completed_models.append(current_design)def show_completed_models(completed_models): #显示打印好的所有模型 print(\"\\nThe following models have been printed: \") for completed_model in completed_models: print(completed_model)unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']completed_models = []print_models(unprinted_designs , completed_models)show_completed_models(completed_models) - 7.4.2 禁止函数修改列表 有时候需要禁止函数修改列表,拿 7.4.1 的例子来说,我们打印了所有设计后,也要保留原来的未打印的设计列表,以供备案,但由于我们将所有的设计都移出了 unprinted_designs,这个列表变成了空的,原来的列表没有了,为了解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件,要将列表的副本传递给函数,可以像下面这样做: 1function_name(list_name[:]) 切片表示法 [:] 创建列表的副本,在 7.4.1 的例子中如果不想清空未打印的设计列表,可像下面这样调用 print_models(): 1print_models(unprinted_designs[:] , completed_models) - 7.5 传递任意数量的实参 Python允许函数从调用语句中收集任意数量的实参 1234def make_pizza(*toppings): print(toppings)make_pizza('pepperoni')make_pizza('mushrooms' , 'green peppers' , 'extra cheese') 形参名 *toppings 中的星号让Python创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中,函数体内的print语句通过生成输出来证明Python能够处理使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形,输出结果如下: 12('pepperoni',)('mushrooms', 'green peppers', 'extra cheese') 使用循环语句: 123456def make_pizza(*toppings): print(\"\\nMaking a pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping)make_pizza('pepperoni')make_pizza('mushrooms' , 'green peppers' , 'extra cheese') 输出结果如下: 12345678Making a pizza with the followiing toppings: - pepperoniMaking a pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.5.1 结合使用位置实参和任意数量实参 如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中: 123456def make_pizza(size , *toppings): print(\"\\nMaking a \" + str(size) + \"-inch pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping)make_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') 输出结果如下: 12345678Making a 16-inch pizza with the followiing toppings: - pepperoniMaking a 18-inch pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.5.2 使用任意数量的关键字实参 有时候,需要接受任何数量的实参,但预先我们不知道传递给函数的会是什么样的信息,在这种情况下,可以将函数编写成能够接受任意数量的键-值对——调用语句提供了多少就接受多少: 12345678910def build_profile(first , last , **user_info): #创建一个字典,其中包括我们知道的有关用户的一切 profile = {} profile['first_name'] = first profile['last_name'] = last for key , value in user_info.items(): profile[key] = value return profileuser_profile = build_profile('albert' , 'einstein' , location = 'princeton' , field = 'physics')print(user_profile) 形参 **user_info 中的两个星号让Python创建一个名为 user_info 的空字典,并将收到的所有名称-值对都封装到这个字典中,在这个函数中,可以像访问其他字典那样访问 user_info 中的名字-值对,程序运行结果如下: 1{'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'} - 7.6 将函数储存在模块中 更进一步,我们可以把函数储存在被称为模块的独立文件中,再将模块导入到主程序中,import 语句运行在当前运行的程序文件中使用模块中的代码 - 7.6.1 导入整个模块 要让函数是可导入的,得先创建模块,模块是扩展名为.py的文件,包含要导入到程序中的代码,下面将创建一个包含函数 make_pizza() 的模块 1234567#pizza.pydef make_pizza(size , *toppings): #概述要制作的比萨 print(\"\\nMaking a \" + str(size) + \"-inch pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping) 接下来,我们在 pizza.py 所在的目录中创建另一个名为 making_pizzas.py 的文件,在这个文件中导入刚刚创建的模块,在调用 make_pizza() 两次: 12345#making_pizzas.pyimport pizzapizza.make_pizza(16 , 'pepperoni')pizza.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') Python在读取这个文件时,代码行 import pizza 让Python打开文件 pizza.py,并在幕后将其中所有函数都复制到这个程序中,在 making_pizzas.py 中,可以使用 pizza.py 中定义的所有函数,要调用被导入的模块中的函数,可指定导入的模块的名称 pizza 和函数名 make_pizza(),并使用句点分隔它们,最终运行结果与原程序相同: 12345678Making a 16-inch pizza with the followiing toppings: - pepperoniMaking a 18-inch pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.6.2 导入特定的函数 导入模块中特定的函数,可以使用以下语法: 1from module_name import function_name 通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:1from module_name import function_0 , function_1 , function_2 以前面的 making_pizzas.py 为例,如果只想导入要使用的函数,代码类似于下面这样:1234from pizza import make_pizzamake_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.3 使用 as 给函数指定别名 如果要导入的函数名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名,要给函数指定别名,需要在导入它的时候这样做,通用语法为:1from module_name import function_name as fn 同样以前面的 making_pizzas.py 为例:1234from pizza import make_pizza as mpmp(16 , 'pepperoni')mp(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.4 使用 as 给模块指定别名 我们还可以给模块指定别名,通用语法为:1import module_name as mn 同样以前面的 making_pizzas.py 为例:1234import pizza as pp.make_pizza(16 , 'pepperoni')p.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.5 导入模块中的所有函数 导入模块中所有函数的通用语法为:1from module_name import * 同样以前面的 making_pizzas.py 为例:1234from pizza import *make_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') import 语句中的星号让Python将模块 pizza 中的每个函数都复制到这个程序中,由于导入了每个函数,可通过名称来调用每个函数,而不需要用句点表示法,然而,如果模块中有函数的名称与项目中的名称相同,就有可能导致意想不到的结果,最佳的做法是,要么只导入我们需要使用的函数,要么导入整个模块并使用句点表示法","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"函数","slug":"函数","permalink":"https://www.itrhx.com/tags/函数/"},{"name":"模块","slug":"模块","permalink":"https://www.itrhx.com/tags/模块/"}]},{"title":"Python3 基础学习笔记 C06","slug":"A12-Python3-basic-C06","date":"2018-10-30T05:42:31.562Z","updated":"2019-09-24T12:45:39.386Z","comments":true,"path":"2018/10/30/A12-Python3-basic-C06/","link":"","permalink":"https://www.itrhx.com/2018/10/30/A12-Python3-basic-C06/","excerpt":"Python3 基础学习笔记第六章 —— 【用户输入和 while 循环】","text":"Python3 基础学习笔记第六章 —— 【用户输入和 while 循环】 - 6.1 函数 input() 的工作原理 函数 input() 让程序暂停运行,等待用户输入一些文本。获取用户输入后,Python将其储存在一个变量当中,以方便你使用;函数 input() 返回为 string 类型 12message = input(\"Please tell me your name:\")print(\"Hello , \" + message + \"!\") 输出结果如下: 12Please tell me your name:anliyHello , anliy! 进阶: 1234message = \"Please tell me your name so that we can personalize the messages you see.\"message += \"\\nWhat's your first name?\"name = input(message)print(\"\\nHello , \" + name + \"!\") 输出结果如下: 1234Please tell me your name so that we can personalize the messages you see.What's your first name?trhxHello , trhx! - 6.1.1 使用 int() 来获取数值输入 使用函数 input() 时,Python会将用户输入解读为字符串: 1234>>> age = input(\"How old are you?\")How old are you?19>>> age'19' 为了解决这个问题,可以使用函数 int() ,它让Python将输入视为数值: 12345>>> age = input(\"How old are you?\")How old are you?19>>> age = int(age)>>> age19 实例: 123456age = input(\"Please tell me your age:\")age = int(age)if age >= 18: print(\"You are old enough to go to the Internet bar!\")else: print(\"You are not old enough to go to Internet bar!\") 输出结果如下: 12Please tell me your age:17You are not old enough to go to Internet bar! - 6.1.2 求模运算符 处理数值信息时,求模运算符(%)是一个很有用的工具,它将两个数相除并返回余数: 12345678>>> 4 % 31>>> 5 % 32>>> 8 % 20>>> 7 % 31 - 6.1.3 在 Python 2.7 中获取输入 如果使用 Python 2.7,应该使用函数 raw_input() 来提示用户输入,这个函数与 Python 3 中的 input() 一样,也将输入解读为字符串;Python 2.7 也包含函数 input(),但它将用户输入解读为Python代码,并尝试运行它们 - 6.2 while 循环 for 循环用于针对集合中的每一个元素的一个代码块,而 while 循环不断地运行,直到指定的条件不满足为止 - 6.2.1 使用 while 循环 一个简单的 while 循环: 1234num = 1while num < 5: print(num) num += 1 输出结果如下: 12341234 - 6.2.2 让用户选择退出循环 123456prompt = \"\\nTell me something, and I will repeat it back to you:\"prompt += \"\\nEnter 'quit' to end the program.\"message = \" \"while message != 'quit': message = input(prompt) print(message) 运行程序: 123456789101112Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.Hello everyone!Hello everyone!Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.Hello again!Hello again!Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.quitquit - 6.2.3 使用标志 在要求很多条件都满足才继续运行的程序中,可以定义一个变量,用于判断整个程序是否处于活动状态,这个变量称为标志 123456789prompt = \"\\nTell me something, and I will repeat it back to you:\"prompt += \"\\nEnter 'quit' to end the program.\"active = Truewhile active: message = input(prompt) if message == 'quit': active = False else: print(message) 运行结果与6.2.2一致 - 6.2.4 使用 break 退出循环 要立即退出 while 循环,不再运行循环中余下的代码,也不管条件测试的结果如何,可使用 break 语句,break 语句用于控制程序流程,可使用它来控制哪些代码将执行,哪些代码不执行 123456789prompt = \"\\nPlease enter the name of a city you have visited:\"prompt += \"\\nEnter 'quit' when you are finished.\"active = Truewhile active: city = input(prompt) if city == 'quit': break else: print(\"I'd love to go to \" + city.title() + \"!\") 运行程序: 1234567891011Please enter the name of a city you have visited:Enter 'quit' when you are finished.ShanghaiI'd love to go to Shanghai!Please enter the name of a city you have visited:Enter 'quit' when you are finished.BeijingI'd love to go to Beijing!Please enter the name of a city you have visited:Enter 'quit' when you are finished.quit 在任何Python循环中都可以使用break语句,例如,可以使用break语句来退出遍历列表或字典 - 6.2.5 在循环中使用 continue 要返回到循环开头,并根据条件测试结果决定是否继续执行循环,可使用 continue 语句,它不像 break 语句那样不再执行余下的代码并退出整个循环,例如,从1到10只打印其中奇数: 123456number =0while number < 10: number += 1 if number % 2 == 0: continue print(number) 输出结果如下:1234513579 - 6.3 使用 while 循环来处理列表和字典 for循环是一种遍历列表的有效方式,但在for循环中不应修改列表,否则将导致Python难以跟踪其中的元素,要在遍历列表的同时对其进行修改,可使用while循环 - 6.3.1 在列表之间移动元素 123456789unconfirmed_users = ['alice' , 'brian' , 'candace']confirmed_users = []while unconfirmed_users: current_user = unconfirmed_users.pop() print(\"Verifying user: \" + current_user.title()) confirmed_users.append(current_user)print(\"\\nThe following users have been confirmed:\")for confirmed_user in confirmed_users: print(confirmed_user.title()) 首先创建一个未验证用户列表,其中包含用户Alice、Brian和Candace,还创建了一个空列表,用于存储已验证的用户,程序中的 while 循环将不断地运行,直到列表 unconfirmed_users 变成空的。在这个循环中,函数pop() 以每次一个的方式从列表 unconfirmed_users 末尾删除未验证的用户。由于Candace位于列表 unconfirmed_users 的末尾,因此其名字将首先被删除、存储到变量 current_user 中并加入到列表 confirmed_users 中。接下来是Brian,然后是Alice 为模拟用户验证过程,我们打印一条验证消息并将用户加入到已验证用户列表中。未验证用户列表越来越短,而已验证用户列表越来越长。未验证用户列表为空后结束循环,再打印已验证用户列表: 12345678Verifying user: CandaceVerifying user: BrianVerifying user: AliceThe following users have been confirmed:CandaceBrianAlice - 6.3.2 删除包含特定值的所有列表元素 可以使用方法 remove() 来删除列表中特定的值,但如果要删除的值在列表中出现了多次,方法 remove() 就不管用了,如果要删除列表中所有包含特定值的元素则可以使用 while 循环: 12345names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']print(names)while 'candace' in names: names.remove('candace')print(names) 输出结果如下: 12['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']['alice', 'alice', 'brian', 'alix', 'heliy'] 使用方法 remove() 做对比: 1234names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']print(names)names.remove('candace')print(names) 输出结果如下: 12['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']['alice', 'alice', 'brian', 'alix', 'candace', 'heliy'] - 6.3.3 使用用户输入来填充字典 12345678910111213141516171819202122responses = {}#设置一个标志,指出调查是否继续polling_active = Truewhile polling_active: #提示输入被调查者的姓名和回答 name = input(\"\\nWhat's your name?\") response = input(\"What kind of fruit do you like?\") #将答卷储存在字典中 responses[name] = response #询问是否还有其他人要参与回答 repeat = input(\"Would you like to let another person respond?(Yes/No)\") if repeat == 'No': polling_active = False#调查结束,显示结果print(\"\\n------ Poll Results ------\")for name , response in responses.items(): print(name + \" like \" + response + \".\") 运行程序: 1234567891011What's your name?TRHXWhat kind of fruit do you like?appleWould you like to let another person respond?(Yes/No)YesWhat's your name?TRHXCCWhat kind of fruit do you like?bananaWould you like to let another person respond?(Yes/No)No------ Poll Results ------TRHX like apple.TRHXCC like banana.","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"input()函数","slug":"input-函数","permalink":"https://www.itrhx.com/tags/input-函数/"},{"name":"while循环","slug":"while循环","permalink":"https://www.itrhx.com/tags/while循环/"}]},{"title":"Python3 基础学习笔记 C05","slug":"A11-Python3-basic-C05","date":"2018-10-27T11:10:10.825Z","updated":"2019-09-24T12:45:35.909Z","comments":true,"path":"2018/10/27/A11-Python3-basic-C05/","link":"","permalink":"https://www.itrhx.com/2018/10/27/A11-Python3-basic-C05/","excerpt":"Python3 基础学习笔记第五章 —— 【字典】","text":"Python3 基础学习笔记第五章 —— 【字典】 - 5.1 一个简单的字典 123fruits = {'apple' : 'red' , 'number' : 5}print(fruits['apple'])print(fruits['number']) 输出结果如下: 12red5 在Python中,字典是一系列键-值对。每个键都与一个值相关联,你可以使用键来访问与之相关联的值。与键相关联的值可以是数字、字符串、列表乃至字典。事实上,可以将任何Python对象用作字典中的值。键-值对是两个相关联的值。在指定键时,Python将返回与之相关联的值。键和值之间用冒号分隔,而键-值对之间用逗号分隔。在字典中,想储存多少个键-值对都可以 - 5.1.1 访问字典中的值 要获取与键相关联的值,可依次指定字典名和放在方括号内的键: 123fruits = {'apple' : 'red' , 'number' : 5}number_fruits = fruits['number']print(\"The number of apple is \" + str(number_fruits) + \"!\") 输出结果如下: 1The number of apple is 5! - 5.1.2 添加键-值对 字典是一种动态结构,可随时在其中添加键-值对。要添加键-值对,可依次指定字典名、用方括号括起来的键和相关联的值 12345fruits = {'apple' : 'red' , 'number1' : 5}print(fruits)fruits['banana'] = 'yellow'fruits['number2'] = 13print(fruits) 输出结果如下: 12{'apple': 'red', 'number1': 5}{'apple': 'red', 'number1': 5, 'banana': 'yellow', 'number2': 13} 注意:键-值对的排列顺序与添加顺序不同。Python不关心键-值对的添加顺序,而只关心键和值之间的关联关系 有时候为了方便也可以先使用一对空的花括号定义一个字典,再分行添加各个键-值对: 1234fruits = {}fruits['banana'] = 'yellow'fruits['number2'] = 13print(fruits) 输出结果如下: 1{'banana': 'yellow', 'number2': 13} - 5.1.3 修改字典中的值 要修改字典中的值,可依次指定字典名、用方括号括起来的键以及与该键相关联的新值 1234fruits = {'color' : 'red'}print(\"The color of the fruits is \" + fruits['color'] + \"!\")fruits['color'] = 'yellow'print(\"The color of the fruits is \" + fruits['color'] + \" now!\") 输出结果如下: 12The color of the fruits is red!The color of the fruits is yellow now! 进阶:对一个能够以不同速度移动的外星人的位置进行跟踪,为此,我们将储存该外星人的当前速度,并据此确定该外星人将向右移动多远: 1234567891011121314alien = {'x_position': 0, 'y_position': 25, 'speed': 'medium'}print(\"Original x-position: \" + str(alien['x_position']))#向右移动外星人,据外星人当前速度决定将其移动多远if alien['speed'] == 'slow': x_increment = 1elif alien['speed'] == 'medium': x_increment = 2else: x_increment = 3#新位置等于老位置加上增量alien['x_position'] = alien['x_position'] + x_incrementprint(\"New x_position: \" + str(alien['x_position'])) 输出结果如下: 12Original x-position: 0New x_position: 2 - 5.1.4 删除键-值对 对于字典中不再需要的信息,可使用del语句将相应的键-值对彻底删除。使用del语句时,必须指定字典名和要删除的键 1234fruits = {'apple' : 'red' , 'number' : 5}print(fruits)del fruits['number']print(fruits) 输出结果如下: 12{'apple': 'red', 'number': 5}{'apple': 'red'} - 5.1.5 由类似对象组成的字典 字典储存的可以是一个对象的多种信息,也可以储存众多对象的同一种信息,例如要调查很多人最喜欢的编程语言: 1234567favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }print(\"Sarah's favorite languages is \" + favorite_languages['sarah'].title() + \"!\") 输出结果如下: 1Sarah's favorite languages is C! - 5.2 遍历字典 - 5.2.1 方法 items() 遍历所有的键-值对 使用for循环来遍历字典:12345678name = { 'username' : 'efermi' , 'first' : 'enrico' , 'last' : 'fermi' , }for key , value in name.items(): print(\"\\nKey: \" + key) print(\"Value: \" + value) 输出结果如下:123456789Key: usernameValue: efermiKey: firstValue: enricoKey: lastValue: fermi for语句的第二部分包含字典和方法items(),它返回一个键-值对列表。接下来,for循环依次将每个键-值对储存到指定的两个变量中 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name, language in favorite_languages.items(): print(name.title() + \"'s favorite language is \" + language.title() + \".\") 输出结果如下: 1234Jen's favorite language is Python.Sarah's favorite language is C.Edward's favorite language is Ruby.Phil's favorite language is Java. - 5.2.2 方法 keys() 遍历字典中所有的键 在不需要使用字典中的值时,方法key()很有用,下面来遍历字典favorite_languages,并将每个被调查者的名字都打印出来: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name in favorite_languages.keys(): print(name.title()) 输出结果如下: 1234JenSarahEdwardPhil 遍历字典时,会默认遍历所有的键,因此,如果将上述代码中的for name in favorite_languages.keys():替换为for name in favorite_languages:输出结果将不变进阶: 1234567891011favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }friends = ['phil', 'sarah']for name in favorite_languages.keys(): print(name.title()) if name in friends: print(\"Hi \" + name + \", I see your favorite languages is \" + favorite_languages[name].title() + \"!\") 输出结果如下: 123456JenSarahHi sarah, I see your favorite languages is C!EdwardPhilHi phil, I see your favorite languages is Java! - 5.2.3 函数 sorted() 按顺序遍历字典中的所有键 字典总是明确地记录键和值之间的关联关系,但获取字典的元素时,获取顺序是不可预测的,要以特定的顺序返回元素,一种办法是在for循环中对返回的键进行排序,为此,可以使用函数sorted()来获得按特定顺序排列的键列表的副本: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name in sorted(favorite_languages.keys()): print(name.title()) 输出结果如下: 1234EdwardJenPhilSarah - 5.2.4 方法 values() 遍历字典中的所有值 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for languages in favorite_languages.values(): print(languages.title()) 输出结果如下: 1234PythonCRubyJava 这种做法提取字典中所有的值,而没有考虑是否重复,为剔除重复项,可使用集合(set),集合类似于列表,但每个元素都必须是独一无二的: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'python' , }for languages in set(favorite_languages.values()): print(languages.title()) 输出结果如下: 123CPythonRuby - 5.3 嵌套 有时候,需要将一系列字典储存在列表中,或将列表作为值储存在字典中,这称为嵌套。可以在列表中嵌套字典、在字典中嵌套列表甚至在字典中嵌套字典 - 5.3.1 字典列表 下面代码创建三个字典,每个字典都表示一个个学生,将这三个字典都放到一个名为students的列表当中,遍历列表将每个学生都打印出来: 123456student_0 = {'name' : 'anily' , 'class' : 2}student_1 = {'name' : 'nikey' , 'class' : 5}student_2 = {'name' : 'heyk' , 'class' : 3}students = [student_0 , student_1 , student_2]for student in students: print(student) 输出结果如下: 123{'name': 'anily', 'class': 2}{'name': 'nikey', 'class': 5}{'name': 'heyk', 'class': 3} 进阶:使用 range() 自动生成三十个外星人: 123456789101112131415#创建一个用于存储外星人的空列表aliens = []#创建三十个绿色的外星人for alien_number in range(30): new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'} aliens.append(new_alien)#显示前五个外星人for alien in aliens[:5]: print(alien)print(\"......\")#显示创建了多少外星人print(\"Total number of aliens: \" + str(len(aliens))) 输出结果如下: 1234567{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}......Total number of aliens: 30 在上述例子中,虽然每个外星人都具有相同特征,但在Python看来,每个外星人都是独立的,我们可以独立地修改每个外星人: 12345678910111213aliens = []for alien_number in range(30): new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'} aliens.append(new_alien)for alien in aliens[0:3]: if alien['color'] == 'green': alien['color'] = 'yellow' alien['points'] = 10 alien['speed'] = 'medium'for alien in aliens[:5]: print(alien)print(\"......\")print(\"Total number of aliens: \" + str(len(aliens))) 输出结果如下: 1234567{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}......Total number of aliens: 30 - 5.3.2 在字典中存储列表 有时候需要将列表储存在字典中,而不是将字典储存在列表中:例一: 12345678910#储存所点比萨的信息pizza = { 'crust' : 'thick' , 'toppings' : ['mushrooms' , 'extra chees'] , }#概述所点的比萨print(\"You ordered a \" + pizza['crust'] + \"-crust pizza\" + \"with the following toppings :\" )for topping in pizza['toppings']: print(\"\\t\" + topping) 输出结果如下: 123You ordered a thick-crust pizzawith the following toppings : mushrooms extra chees 例二: 12345678910favorite_languages = { 'jen' : ['python' , 'ruby'] , 'sarah' : ['c'] , 'edward' : ['go' , 'ruby'] , 'phil' : ['python' , 'java'] , }for name , languages in favorite_languages.items(): print(\"\\n\" + name.title() + \"'s favorite languages are:\") for language in languages: print(\"\\t\" + language.title()) 输出结果如下: 123456789101112131415Jen's favorite languages are: Python RubySarah's favorite languages are: CEdward's favorite languages are: Go RubyPhil's favorite languages are: Python Java - 5.3.3 在字典中存储字典 123456789101112131415161718users = { 'aeinstein' : { 'first' : 'albert' , 'last' : 'einstein' , 'location' : 'princeton' , } , 'mcurie' : { 'first' : 'marie' , 'last' : 'curie' , 'location' : 'paris' , } , }for username , user_info in users.items(): print(\"\\nUsername : \" + username) full_name = user_info['first'] + \" \" + user_info['last'] location = user_info['location'] print(\"\\tFull name : \" + full_name.title()) print(\"\\tlocation : \" + location .title()) 输出结果如下: 12345678Username : aeinstein Full name : Albert Einstein location : PrincetonUsername : mcurie Full name : Marie Curie location : Paris","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"字典","slug":"字典","permalink":"https://www.itrhx.com/tags/字典/"}]},{"title":"Python3 基础学习笔记 C04","slug":"A10-Python3-basic-C04","date":"2018-10-23T16:57:39.886Z","updated":"2019-09-24T12:45:32.554Z","comments":true,"path":"2018/10/24/A10-Python3-basic-C04/","link":"","permalink":"https://www.itrhx.com/2018/10/24/A10-Python3-basic-C04/","excerpt":"Python3 基础学习笔记第四章 —— 【if语句】","text":"Python3 基础学习笔记第四章 —— 【if语句】 - 4.1 一个简单的数列 给定一个汽车列表,将其中每一辆汽车的名称打印出来,要求打印 ‘bmw’ 时所有字母都要大写,其余名称只需要首字母大写: 123456cars = ['audi' , 'bmw' , 'subaru' , 'toyota']for car in cars: if car == 'bmw': print(car.upper())else: print(car.title()) 输出结果如下: 1234AudiBMWSubaruToyota - 4.1.1 检查特定值是否包含在列表当中 要判断特定的值是否已包含在列表当中,可使用关键字 in 1234user_names = ['andia' , 'david' , 'liwa']user = 'andia'if user in user_names: print(user.title() + \"is in user_name.\") 输出结果如下: 1Andiais in user_name. 要判断特定的值是否不包含在列表当中,可使用关键字 not in 1234user_names = ['andia' , 'david' , 'liwa']user = 'kivle'if user not in user_names: print(user.title() + \"is not in user_name.\") 输出结果如下: 1Kivleis not in user_name. - 4.2 if-else 语句 1234567age = input(\"请输入你的年龄查看是否可以去网吧:\")if int(age) >= 18: print(\"You are old enough to go to the net bar!\") print(\"You should go to net bar less,study more!\")else: print(\"You are too young to go to the net bar!\") print(\"Wait until you are 18 to go to the net bar!\") 分别输入19和15,输出结果如下: 123请输入你的年龄查看是否可以去网吧:19You are old enough to go to the net bar!You should go to net bar less,study more! 123请输入你的年龄查看是否可以去网吧:15You are too young to go to the net bar!Wait until you are 18 to go to the net bar! - 4.3 if-elif-else 结构 12345678age = 12if age < 4: price = 0elif age < 18: price = 5else: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果如下: 1Your admission cost is $5. - 4.3.1 使用多个 elif 代码块 12345678910age = 20if age < 4: price = 0elif age < 18: price = 5elif age < 65: price = 15else: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果如下: 1Your admission cost is $15. - 4.3.2 省略 else 代码块 Python并不要求 if-elif 结构后面必须有 else 代码块: 12345678910age = 20if age < 4: price = 0elif age < 18: price = 5elif age < 65: price = 15elif age >= 65: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果仍与3.3.1一样 - 4.4 测试多个条件 if-elif-else结构功能强大,但仅适用于只有一个条件满足的情况:遇到通过了的测试后,Python就会跳过余下的测试: 12345678 names = ['Zhangshan' , 'Wanger']if 'Zhangshan' in names: print(\"Zhangshan is here!\")if 'Wanger' in names: print(\"Wanger is here!\")if 'Xiaoming' in names: print(\"Xiaoming is here!\")print(\"All the students are here!\") 输出结果如下: 123Zhangshan is here!Wanger is here!All the students are here! 相同的程序,如果使用 if-elif-else 结构,代码将不能正确运行: 12345678names = ['Zhangshan' , 'Wanger']if 'Zhangshan' in names: print(\"Zhangshan is here!\")elif 'Wanger' in names: print(\"Wanger is here!\")elif 'Xiaoming' in names: print(\"Xiaoming is here!\")print(\"All the students are here!\") 输出结果如下:12Zhangshan is here!All the students are here! 总之,如果我们只想执行一个代码块,就使用 if-elif-else 结构;如果要运行多个代码块,就必须使用一系列独立的 if 语句! - 4.5 使用 if 语句处理列表 - 4.5.1 检查特殊元素对3.4例子改版,加入姓名 ‘Xiaoming’,当检索到Xiaoming时告诉他,他妈妈叫他回家吃饭1234567names = ['Zhangshan' , 'Wanger' , 'Xiaoming']for name in names: if name == 'Xiaoming': print(\"Xiaoming,Your mother told you to go home for dinner!\") else: print(name +\"is here!\")print(\"All the students are here!\") 输出结果如下: 1234Zhangshanis here!Wangeris here!Xiaoming,Your mother told you to go home for dinner!All the students are here! - 4.5.2 确定列表不是空的 在检索姓名前检查姓名是否为空,不为空则打印出所有姓名,为空则提示没有姓名: 1234567names = []if names: for name in names: print(name +\" is here!\") print(\"All the students are here!\")else: print(\"There is no students!\") 输出结果如下: 1There is no students! 在if语句中将列表名用在条件表达式中时,Python将在列表至少包含一个元素时返回Ture,并在列表为空时返回False - 4.5.3 使用多个列表 两个列表names_1和names_2,要求输出既在names_2中又在names_1中的元素: 123456names_1 = ['Zhangshan' , 'Liyang' , 'Wanger' , 'Tangyang' , 'Xiaoming']names_2 = ['Liyang' , 'Zhangwei' , 'Tangyang']for names in names_2: if names in names_1: print(names +\" is here!\")print(\"All the students are here!\") 输出结果如下: 123Liyang is here!Tangyang is here!All the students are here!","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"if语句","slug":"if语句","permalink":"https://www.itrhx.com/tags/if语句/"}]},{"title":"Python3 基础学习笔记 C03","slug":"A09-Python3-basic-C03","date":"2018-10-11T15:03:18.487Z","updated":"2019-09-24T12:45:28.649Z","comments":true,"path":"2018/10/11/A09-Python3-basic-C03/","link":"","permalink":"https://www.itrhx.com/2018/10/11/A09-Python3-basic-C03/","excerpt":"Python3 基础学习笔记第三章 —— 【操作列表】","text":"Python3 基础学习笔记第三章 —— 【操作列表】 - 3.1遍历整个列表 使用 for 循环来遍历整个列表: 123names = ['alice' , 'david' , 'liwei']for name in names:print(name) 输出结果如下: 123alicedavidliwei for循环让Python从列表names中取出一个名字,并将其储存在变量name中,最后 让Python打印前面储存到变量name中的名字,对于列表中的每个名字,Python都将 重复执行后两行代码,将列表names中的每个名字都打印出来 - 3.1.1在for循环中执行更多的操作 在for循环中,可对每个元素执行任何操作,下面对前面的示例进行扩展: 例一:123names = ['alice' , 'david' , 'liwei']for name in names: print(name.title() + \", that was a good man!\") 输出结果如下: 123Alice, that was a good man!David, that was a good man!Liwei, that was a good man! 例二: 12345names = ['alice' , 'david' , 'liwei']for name in names: print(name.title() + \", that was a good man!\") print(\"I can't wait to see you again,\" + name.title() + \".\\n\")print(\"Nice to meet you!\") 输出结果如下: 12345678910Alice, that was a good man!I can't wait to see you again,Alice.David, that was a good man!I can't wait to see you again,David.Liwei, that was a good man!I can't wait to see you again,Liwei.Nice to meet you! - 3.2 range()函数 Python使用range()函数能够轻松地生成一系列的数字 Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表; Python3 list() 函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表; Python2 range() 函数返回的是列表 例一:12for i in range(1,5): print(i) 输出结果如下: 12341234 例二:12for i in range(5): print(i) 输出结果如下:1234501234 例三:123456789101112>>> list(range(5))[0, 1, 2, 3, 4]>>> list(range(0))[]>>>list(range(0, 30, 5))[0, 5, 10, 15, 20, 25]>>> list(range(0, 10, 2))[0, 2, 4, 6, 8]>>> list(range(0, -10, -1))[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]>>> list(range(1, 0))[] 例四: 12345squares = []for value in range(1,11): square = value ** 2 squares.append(square)print(squares) 输出结果如下: 1[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] - 3.2.1 对数字列表执行简单的统计计算 1234567>>> digits = [1, 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0]>>> min(digits)0>>>max(digits)9>>>sum(digits)45 - 3.2.2 列表解析 列表解析能够让比如3.2中的例四更加简化,只需要一行代码就能生成这样的列表,列表解析将for循环和创建新元素的代码合并成一行,并自动附加新元素: 12squares = [value ** 2 for value in range(1,11)]print(squares) 在这个示例中,for循环为for value in range(1,11),它将值1~10提供给表达式value ** 2输出结果如下: 1[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] - 3.3 使用列表的一部分 处理列表的部分元素——Python称之为切片 - 3.3.1 切片 1234567891011list = ['a','b','c','d','e','f']print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾print(list[4:1]) #从左到右索引,因此为空值print(list[-1:-3]) #从左到右索引,因此为空值print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2print(list[::-1]) #反向取值,反向输出列表 - 3.3.2 遍历列表 1234players = ['charles' , 'martina' , 'michael' , 'florence' , 'eli']print(\"Here are the first three players on my team:\")for player in players[:3]: print(player.title()) 输出结果如下: 1234Here are the first three players on my team:CharlesMartinaMichael - 3.3.3 复制列表 要复制列表,可以创建一个包含整个列表的切片,方法是同时省略起始索引和终止索引([:]),这让Python创建一个始于第一个元素,终止于最后一个元素的切片,即复制整个列表: 123456my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods[:]print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake'] 为核实我们的确有两个列表,下面在每个列表中都添加一种食品,并核实每个列表都记录了相应人员喜欢的食品:12345678910my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods[:]my_foods.append('cannoli')friend_foods.append('ice cream')print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake', 'ice cream'] 输出结果表明,’cannoli’包含在我喜欢的食品列表中,而’ice cream’没有;’ice cream’包含在我朋友喜欢的食品中,而’cannoli’没有,假如我们只是简单的将my_foods赋给friend_foods,就不能得到两个列表。下面是错误示例: 12345678910my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods #错误写法my_foods.append('cannoli')friend_foods.append('ice cream')print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 错误示例输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream'] - 3.4 元组 Python将不能修改的值称为不可变的,而不可变的列表被称为元组 - 3.4.1 定义元组 元组看起来就像是列表,但元组使用圆括号而不是方括号来标识,定义元组后,就可以使用索引来访问其元素,就像访问列表元素一样: 123dimensions = (200,50)print(dimensions[0])print(dimensions[1]) 输出结果如下: 1220050 如果尝试修改元组中元素的值,将会导致Python返回类型错误消息,由于试图修改元组的操作是被禁止的,因此Python指出不能给元组的元素赋值: 12dimensions = (200,50)dimensions[0] = 300 将会报错: 1234Traceback (most recent call last): File \"dimensions.py\", line 2, in <module> dimensions[0] = 300TypeError: 'tuple' object does not support item assignment - 3.4.2 遍历元组中所有的值 像列表一样,元组也可以使用for循环来遍历元组中的所有值: 例一:123dimensions = (200,100,50,6)for dimension in dimensions: print(dimension) 输出结果如下: 1234200100506 例二: 123dimensions = (200,100,50,6)for dimension in dimensions[:3]: print(dimension) 输出结果如下: 12320010050 - 3.4.3 修改元组变量 虽然不能修改元组元素,但是可以给储存元组的变量赋值: 123456789dimensions = (200,50)print(\"Original dimensions:\")for dimension in dimensions: print(dimension) dimensions = (400,100)print(\"\\nModified dimensions:\")for dimension in dimensions: print(dimension) 输出结果如下: 1234567Original dimensions:20050Modified dimensions:400100 我们首先定义了一个元组,并将其储存的尺寸打印了出来;然后将一个新元组储存到变量dimensions中,打印新的尺寸;相比于列表,元组是更简单的数据结构。如果需要储存的一组值在程序的整个生命周期内都不变,可使用元组","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"操作列表","slug":"操作列表","permalink":"https://www.itrhx.com/tags/操作列表/"}]},{"title":"Python3 基础学习笔记 C02","slug":"A08-Python3-basic-C02","date":"2018-09-15T17:59:48.972Z","updated":"2019-09-24T12:45:24.985Z","comments":true,"path":"2018/09/16/A08-Python3-basic-C02/","link":"","permalink":"https://www.itrhx.com/2018/09/16/A08-Python3-basic-C02/","excerpt":"Python3 基础学习笔记第二章 —— 【列表】","text":"Python3 基础学习笔记第二章 —— 【列表】 - 2.1列表是什么 列表由一系列按特定顺序的元素组成,在 Python 中用方括号( [ ] )来表示列表,并用逗号来分隔其中的元素,例: 12345list1 = ['a','b','c','d','e','f']list2 = ['abc', 'xyz', 2018, 2020]list3 = [1, 2, 3, 4, 5 ,6]list4 = [\"a\", \"b\", \"c\", \"d\"]print(list1, list2, list3 ,list4) 输出结果如下: 1['a', 'b', 'c', 'd', 'e', 'f'] ['abc', 'xyz', 2018, 2020] [1, 2, 3, 4, 5, 6] ['a', 'b', 'c', 'd'] - 2.1.1访问列表元素 列表是有序集合,因此要访问列表的元素,只需要将该元素的位置或索引告诉Python即可,注意:在Python中的第一个列表元素的索引为0,而不是1 12345list = ['a','b','c','d','e','f']print(list[0])print(list[3])print(list[-1]) #Python为访问最后一个列表元素提供了一种特殊语法,通过将索引指定为-1,可以让Python返回最后一个列表元素print(list[-3]) 输出结果如下: 1234adfd - 2.1.2列表切片 1234567891011list = ['a','b','c','d','e','f']print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾print(list[4:1]) #从左到右索引,因此为空值print(list[-1:-3]) #从左到右索引,因此为空值print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2print(list[::-1]) #反向取值,反向输出列表 输出结果如下: 12345678910['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c']['d', 'e', 'f']['b', 'c', 'd'][][]['d', 'e']['b', 'd']['f', 'e', 'd', 'c']['f', 'e', 'd', 'c', 'b', 'a'] - 2.1.3使用列表中的各个值 可像使用其他变量一样使用列表中的各个值,例如,我们可以使用拼接根据列表中的值来创建消息: 123list = ['python', 'c', 'c++', 'java', 'php']message = \"My favorite language is \" + list[0].title() + \"!\"print(message) 输出结果如下: 1My favorite language is Python! - 2.1.4修改元素 修改列表元素的语法与访问列表元素的语法类似,要修改列表元素,可指定列表名和要修改的元素的索引,再次指定该元素的新值: 1234names = ['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']print(names)names[1] = 'lifang'print(names) 输出结果如下: 12['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']['zhangsan', 'lifang', 'wanger', 'liming', 'xiaowang'] - 2.1.5添加元素 - 使用方法 append() 在列表末尾添加元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.append('g')print(list)输出结果如下:12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'd', 'e', 'f', 'g'] - 使用方法 insert() 在列表指定位置添加元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.insert(2,\"h\") #其中括号里的数字表示要插入的位置,此后面的元素将右移一个位置print(list) 输出结果如下: 12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'h', 'c', 'd', 'e', 'f', 'g'] - 2.1.6删除元素 - 使用 del 语句删除元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)del list[3]print(list) 输出结果如下: 12list = ['a', 'b', 'c', 'd', 'e', 'f']list = ['a', 'b', 'c', 'e', 'f'] - 使用方法 pop() 删除最后一个元素方法 pop() 可以删除列表末尾的元素,并让你能够接着使用它。术语弹出(pop)源自这样的类比:列表就像是一个栈,而删除列表末尾的元素就相当于弹出栈顶元素:12345list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)new_list = list.pop()print(list)print(new_list)输出结果如下:123['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'd', 'e']f - 使用方法 pop() 删除任意位置元素可以使用 pop() 来删除列表中任何位置的元素,只需要在括号中指定要删除的元素的索引即可:12345list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)new_list = list.pop(1)print(list)print(new_list)输出结果如下:123['a', 'b', 'c', 'd', 'e', 'f']['a', 'c', 'd', 'e', 'f']b - 使用方法 remove() 删除未知位置元素当我们不知道元素的位置,只知道元素的值的时候,就可以使用方法 remove()1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.remove('d')print(list)输出结果如下:12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'e', 'f'] # - 2.1.7使用方法 index() 查找指定元素位置 12list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]print(list.index('c')) 输出结果如下: 12 - 2.1.8使用方法 count() 统计指定元素数量 12list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]print(list.count('a')) 输出结果如下: 12 - 2.1.9清空列表 123list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]list.clear()print(list) 输出结果如下: 1[] - 2.2组织列表 在创建的列表中,元素的排列顺序常常是无法预测的,因为我们并非总能控制用户提供数据的顺序。有时候,我们希望保留列表元素最初的排列顺序,而有时候又需要调整排列顺序。Python提供了很多组织列表的方式,可根据具体情况选用 - 2.2.1使用方法 sort() 对列表进行永久排序 使用方法 sort() 可以对列表按照特殊符号,数字,大写字母,小写字母顺序进行永久排序: 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.sort()print(cars) 输出结果如下: 1['audi', 'bmw', 'subaru', 'toyota'] 还可以按与字母顺序相反的顺序排列列表元素,只需要向 sort() 方法传递参数 reverse = True 就可以了: 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.sort(reverse = True)print(cars) 输出结果如下: 1['toyota', 'subaru', 'bmw', 'audi'] - 2.2.2使用函数 sorted() 对列表进行临时排序 要保留列表元素原来的排列顺序,同时以特定的顺序呈现它们,可使用函数sorted()。函数sorted()让你能够按特定顺序显示列表元素,同时不影响它们在列表中的原始排列顺序: 123456789cars = ['bmw', 'audi', 'toyota', 'subaru']print(\"Here is the original list:\")print(cars)print(\"\\nHere is the sorted list:\")print(sorted(cars))print(\"\\nHere is the sorted reverse list:\")print(sorted(cars, reverse=True))print(\"\\nHere is the original list again:\")print(cars) 输出结果如下: 1234567891011Here is the original list:['bmw', 'audi', 'toyota', 'subaru']Here is the sorted list:['audi', 'bmw', 'subaru', 'toyota']Here is the sorted reverse list:['toyota', 'subaru', 'bmw', 'audi']Here is the original list again:['bmw', 'audi', 'toyota', 'subaru'] - 2.2.3使用方法 reverse() 对列表进行反向排序 要反转列表元素的排列顺序,可使用方法 reverse() 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.reverse()print(cars) 输出结果如下: 1['subaru', 'toyota', 'audi', 'bmw'] - 2.2.4确定列表的长度 使用函数 len() 可以快速获悉列表的长度: 123>>>cars = ['bmw', 'audi', 'toyota', 'subaru']>>>len(cars)4 - 2.2.5合并列表 - 使用方法 extend() 合并列表 12345list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1.extend(list2) #将列表list2添加到list1当中去print(list1)print(list2) 输出结果如下: 12[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd'] - 使用 “+” 号合并列表 1234list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']print(list1 + list2)print(list2 + list1) 输出结果如下: 12[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd', 1, 2, 3, 4] - 使用切片合并列表 1234567891011121314list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[len(list1) : len(list1)] = list2 #len(list1)代表要将list2插入list1中的位置print(list1)list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[0 :0] = list2print(list1)list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[1:1] = list2print(list1) 输出结果如下: 123[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd', 1, 2, 3, 4][1, 'a', 'b', 'c', 'd', 2, 3, 4]","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"列表","slug":"列表","permalink":"https://www.itrhx.com/tags/列表/"}]},{"title":"Python3 基础学习笔记 C01","slug":"A07-Python3-basic-C01","date":"2018-09-13T12:47:09.608Z","updated":"2019-12-29T07:27:42.544Z","comments":true,"path":"2018/09/13/A07-Python3-basic-C01/","link":"","permalink":"https://www.itrhx.com/2018/09/13/A07-Python3-basic-C01/","excerpt":"Python3 基础学习笔记第一章 —— 【变量和简单数据类型】","text":"Python3 基础学习笔记第一章 —— 【变量和简单数据类型】 - 1.1变量的命名和使用 变量名只能包含字母、数字和下划线。变量名可以字母或者下划线打头,但不能以数字开头,例如,可以将变量命名为message_1,但不能将其命名为1_message 变量名不能包含空格,但可使用下划线来分割其中的单词,例如,变量名greeting_message可行,但变量名greeting message会引发错误 不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词,如print 变量名应既简短又具有描述性,例如,name比n好,student_name比s_n好,name_length比length_of_persons_name好 慎用小写字母l和大写字母O,因为它们可能被人看错成数字1和0 - 1.2字符串 字符串就是一系列字符,在Python中,用引号括起来的都是字符串,其中的引号可以是单引号也可以双引号: 12\"This is a string.\"'This is also a string.' 这种灵活性让我们能够在字符串中包含引号和撇号: 123'I told my friend,\"Python is my favorite language!\"'\"The language 'Python' is named er Monty Python,not the snake.\"\"One of Python's strengths is i diverse and supportive community.\" - 1.2.1使用方法修改字符串的大小写三种处理方法如下:123title() #将字符串每个单词的首字母都改为大写upper() #将字符串的每个字母都改为大写lower() #将字符串的每个字母都改为小写 例如:1234message = \"I love you!\"print(name.title())print(name.upper())print(name.lower()) 输出结果如下:123I Love You!I LOVE YOU!i love you! - 1.2.2合并(拼接)字符串Python使用加号(+)来合并字符串,举例说明: 12345first_name = \"I\"second_name = \"love\"third_name = \"python\"full_name = first_name + \" \" + second_name + \" \" + third_timeprint(full_name.title() + \"!\") 输出结果如下: 1I Love Python! - 1.2.3使用制表符或换行符来添加空白添加横向制表符: 12>>>print(\"\\tPython\") Python 添加换行符: 12345>>>print(\"C\\nC++\\nPython\\nJavaScript\")CC++PythonJavaScript 附表:Python转义符 - 1.2.4删除空白在Python中可用 lstrip()、rstrip()、strip() 分别删除字符串开头、结尾、全部的空白,举例说明: 123456789>>>message = ' python '>>>message' python '>>>message.lstrip()'python '>>>message.rstrip()' python'>>>message.strip()'python' 如果要永久删除字符串中的空白,必须将删除操作的结果存回到变量中: 1234>>>message = ' python '>>>message = message.strip()>>>message'python' - 1.3数字在编程中,经常使用数字来记录游戏得分、表示可视化数据、储存Web应用信息等。Python根据数字的用法以不同的方式处理它们 - 1.3.1整数在Python中,可对整数执行加(+)减(-)乘(*)除(/)乘方(**)运算,同时也支持运算次序: 12345678910111213141516>>>3 + 25>>>3 - 21>>>3 * 26>>>3 \\ 21.5>>>3 ** 29>>>3 ** 327>>>2 + 3 * 414>>>(2 + 3) * 420 - 1.3.2浮点数Python将带小数点的数字都称为浮点数: 1234>>>0.1 + 0.10.2>>>2 * 0.20.4 需要注意的是,结果包含的小数位可能是不确定的,就现在而言,暂时忽略多余的小数位即可: 1234>>>0.2 + 0.10.30000000000000004>>>3 * 0.10.30000000000000004 - 1.3.3使用函数 str() 避免错误错误例子: 123age = 23message = \"Happy \" + age + \"rd Birthday!\"print(message) 运行时会报错: 1234Traceback (most recent call last): File \"birthday.py\", line 2, in <module> message = \"Happy \" + age + \"rd Birthday!\"TypeError: must be str, not int 这是一个类型错误,意味着Python无法识别我们使用的信息。在这个例子中,Python发现我们使用了一个值为整数(int)的变量,但它不知道该如何解读这个值,这个变量表示的可能是数值23,也可能是字符2和3。像上面这样的字符串中使用整数时,需要显式地指出我们希望Python将这个整数用作字符串。为此,可调用函数 str(),它让Python将非字符串值表示为字符串: 123age = 23message = \"Happy \" + str(age) + \"rd Birthday!\"print(message) 输出结果如下: 1Happy 23rd Birthday! - 1.4注释注释让我们能够使用自然语言在程序中添加说明,Python中注释有三种方法: 123456789print(\"Hello Python!\")#这是单行注释'''这是多行注释这是多行注释'''\"\"\"这也是多行注释这也是多行注释\"\"\"","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"数据类型","slug":"数据类型","permalink":"https://www.itrhx.com/tags/数据类型/"},{"name":"变量","slug":"变量","permalink":"https://www.itrhx.com/tags/变量/"}]},{"title":"VMware Pro 14 安装 Ubuntu 18.04 详细教程","slug":"A06-install-ubuntu18.04","date":"2018-09-09T13:14:29.532Z","updated":"2019-09-09T14:00:24.009Z","comments":true,"path":"2018/09/09/A06-install-ubuntu18.04/","link":"","permalink":"https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/","excerpt":"","text":"1.下载安装 VMware Workstation Pro 14 进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装 2.下载 Ubuntu 18.04 系统 进入 Ubuntu 官网,下载最新版 Ubuntu 系统镜像 3.在 VMware 中创建虚拟机打开安装好的 VMware Workstation Pro 14,选择创建新的虚拟机 在新建虚拟机向导中选择自定义(高级) 默认直接下一步,直到出现下图,再选择稍后安装操作系统 选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本 更改虚拟机名称及存放位置 为虚拟机指定处理器数量,默认即可 为虚拟机分配内存,太大了可能会导致卡顿,太小了也不好,推荐内存大小即可 以下均选择默认即可 选择创建新虚拟磁盘 选择将虚拟磁盘储存为单个文件 默认下一步 点击完成 此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Ubuntu 64 位,单击 Ubuntu 64 位,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Ubuntu 64 位镜像文件,点击“确定” 4.在虚拟机上安装 Ubuntu 系统单击 Ubuntu 64 位,选择“开启此虚拟机” 来到欢迎界面,选择好语言,点击“安装 Ubuntu” 选择键盘布局为“汉语” 更新和其他软件默认选择即可 安装类型选择“清除整个磁盘并安装 Ubuntu”,PS: 因为我们是新安装的系统,且在虚拟机中,所以可以选择清除整个磁盘,这个操作不会清除你原来电脑里面的东西 地区随便,在中国就行,默认即可 之后设置计算机名,密码 点击继续稍等一会就安装完成啦 安装过程中可能会出现的一些问题 1.在虚拟机上安装 Ubuntu 系统的过程中卡死不动 解决方法:关闭网络,重新安装即可 2.Ubuntu 不能全屏显示解决方法:方法①:安装 open-vm-tools: 1sudo apt-get install open-vm-tools 然后执行: 1sudo apt-get install open-vm* 重启即可全屏显示 方法②:在终端输入xrandr,并回车,我们就可以看到很多可以修改的分辨率,选择好分辨率后,比如我们要修改分辨率为 1920x1440 ,则在终端输入 xrandr -s 1920x1440,回车即可,注意 1920x1440 中间是小写字母 x,本人亲测此方法并不是很完美,不能完全适应屏幕 方法③:安装 VMware Tools:1、进入 Ubuntu 系统后,点击虚拟机上的【虚拟机】—>【安装 VMware Tools】,回到桌面即可看到一个 VMware Tools 的 图标2、复制 VMwareTools-10.0.10-4301679.tar.gz(版本根据自己的实际情况而定)到 home 目录下, 用命令 tar -xzvf VMwareTools-10.0.10-4301679.tar.gz 进行解压3、解压后 cd vmware_tools_distrib,打开终端4、输入“sudo ./vmware-install.pl”,输入用户密码后开始安装5、接下来会有很多地方需要你按 Enter或者 Yes6、当你看到出现 —the vmware team 的字样后就可以关闭窗口了,此时窗口就会自动全屏了,如果没有全屏,重启过后就可以了7、若还没有全屏显示,则将虚拟机的【查看】—>【自动调整大小】—>【自适应客户机】,都选上,即可实现全屏","categories":[{"name":"Linux","slug":"Linux","permalink":"https://www.itrhx.com/categories/Linux/"}],"tags":[{"name":"VMware","slug":"VMware","permalink":"https://www.itrhx.com/tags/VMware/"},{"name":"Ubuntu","slug":"Ubuntu","permalink":"https://www.itrhx.com/tags/Ubuntu/"}]},{"title":"主流 Markdown 编辑器推荐","slug":"A05-markdown-editor","date":"2018-08-29T15:02:46.857Z","updated":"2019-09-09T13:40:31.217Z","comments":true,"path":"2018/08/29/A05-markdown-editor/","link":"","permalink":"https://www.itrhx.com/2018/08/29/A05-markdown-editor/","excerpt":"","text":"Markdown ,2004年由 John Gruberis 设计和开发,是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式,以下将介绍目前比较流行的一些 Markdown 编辑器(排名不分先后) - MarkdownPad 目前分为 MarkdownPad2 和 MarkdownPad Pro 版本,后者收费,我们使用前者足矣,用户可以通过键盘快捷键和工具栏按钮来使用或者移除 Markdown 各种语法格式,支持自定义配色方案、字体、大小和布局 、即时HTML预览、HTML和PDF导出,被很多人称赞为 Windows 平台最好用的 Markdown 编辑器,实用性强,仅支持 Windows 系统,个人觉得在 Windows 10 系统上界面并不是很好看,有时候添加音乐什么的,资源多了,实时预览会显示资源加载失败,点击此处访问 MarkdownPad 官网 - BookPad 无意间在 Microsoft Store 上发现的,完美搭配 Win10 系统,界面非常简洁漂亮,2017年9月份发布,大小30.82 MB,官方网站:https://sosfos.wordpress.com/ ,收费13人民币,可免费使用7天,各种功能应有尽有,和其他编辑器不相上下,本来想着百度百度看看有没有破解版,结果全网看不见 BookPad 的影子,估计是新出来的还不为人所知吧,可以直接在 Microsoft Store 搜索下载,或者点击链接获取:https://www.microsoft.com/store/apps/9N6P5ZH2SJSX - 小书匠 分为免费版和收费版,收费版¥20/年,其实免费版的功能已经足够强大了,多种编辑模式、多种主题选择、多种编辑器实现、丰富的语法支持、第三方同步、强大的文件管理功能,让人使用一次就爱上了它,支持 Windows 和 Web,推荐使用,点击此处访问小书匠官网 - TyporaTypora 同样支持 Windows、OS X 和 Linux,Typora 支持即时渲染技术,这也是与其他 Markdown 编辑器最显著的区别,支持数学编辑,可与 Word 直接格式转换,在 Pandoc 的支持下进行多种文档格式转换,Typora 适合那些对码字手速和排版顺畅度有要求的人群,譬如码农、网站小编等,点击此处访问 Typora 官网 - Visual Studio CodeVisual Studio Code 是众所周知的神器,是微软推出一款轻量级的文本编辑工具,类似于 Sublime,它已经默认集成 Markdown 文档编辑插件,原生就支持高亮 Markdown 的语法,但想要实时预览还需要选择 Markdown: Open Preview to the Side 命令实现,相关教程请点击此处,点击此处 访问 Visual Studio Code 官网 - MarxicoMarxico 中文名马克飞象,提供桌面客户端以及离线 Chrome App,支持移动端 Web,可以直接把文本存到印象笔记,点击此处访问 Marxico,点击此处访问 马克飞象 - Sublime Text 3Sublime Text 3 是基于 Vim 开发的跨平台代码编辑器,收费80美元,好像可以免费试用,支持 OS X、Windows、Ubuntu 等 UNIX 及 Linux 操作系统,由于其功能的多样性而广受好评,界面简约大方,定位专业,原生支持的编程语言就多达十几种,通过第三方插件,还能实现更多语法的支持,其中就包括 Markdown ,但也有个缺点,就是不能实时预览,但是用户可以通过 Markdown Preview 的插件实现对 Markdown 的预览,具体教程请点击此处查看,点击此处访问 Sublime Text 官网 - Mou Mou 是一款由国人独立开发者罗晨开发的实时预览型 Markdown 编辑器,仅支持 OS X操作系统,是目前同类应用中对汉字兼容性最好的作品,也是目前最好用的免费 Markdown 编辑器,提供语法高亮、在线预览、同步滚动、全屏模式,支持自定保存、自动匹配,允许自定义主题,支持 CSS,HTML 和 PDF 导出等功能,点击此处访问 Mou 官网 - AtomAtom 是 Github 专门为程序员推出的一个跨平台文本编辑器,具有简洁和直观的图形用户界面,并有很多有趣的特点:支持CSS,HTML,JavaScript等网页编程语言,当然也支持 Markdown ,支持宏,自动完成分屏功能,集成了文件管理器,点击此处访问 Atom 官网 - Smark国人编写的开源软件,Windows / Linux 等主流系统跨平台支持,完美支持 LaTex 数学公式、脚注、尾注等,支持使用本地 MathJax 调用,不需要在线访问 MathJax CDN,用户可配置的 Markdown 语法高亮显示,美观整洁,多种格式文件导出支持,简洁友好的界面布局,完备的各类快捷键,能极大地提高工作效率,点击此处访问 Smark 官网 - HaroopadHaroopad 覆盖三大主流桌面系统,支持 Windows、OS X 和 Linux,多种主题样式供你选择,语法标亮支持 54 种编程语言,该工具重点推荐 Ubuntu/Linux 用户使用,点击此处访问 Haroopad 官网 - CuteMarkEdCuteMarkEd 是一个基于qt5的跨平台的 Markdown 编辑器,开源的, 提供实时 HTML 预览、数学表达式、源码高亮和PDF导出,点击此处 访问 CuteMarkEd 官网 - MarkPadMarkPad 是款开源的 Markdown 编辑器,与 Window 8 风格和谐友好的界面,可以直接在你的博客或者 GitHub 中打开、保存文档,直接将图片粘贴到 Markdown 文档中,点击此处访问 MarkPad 官网 - Cmd Markdown作业部落出品,是一款不错的工具和博客平台兼顾的产品,同时支持 Linux、Mac 和 Windows 操作系统,此外还提供 Web 在线创作,社交化批注、智能云同步,最简单的方法,满足多种写作需要,点击此处访问 Cmd Markdown 官网 - FarBox同样是一款不错的 Markdown 编辑器和博客平台兼顾的产品,让用户通过Dropbox(现在默认是自己的同步服务器)直接建立个人网站。FarBox编辑器免费,同时支持 Linux、Mac 和 Windows 操作系统,Farbox服务可以免费试用,在本地编辑器内写作自动同步发布在个人博客,对于希望有个人博客但却不愿折腾的小白来说,是个不错的选择,点击此处访问 FarBox 官网 - MiuMiu 是一款 Windows 下的 Markdown 编辑器,支持 Markdown 高亮、代码高亮、即时预览,以及可以快速发布到 Github Gist,小众软件,界面美观,已经找不到官网了,小众软件网有提供百度云下载,Miu 下载地址 - MacDownMacDown 引用了许多 Mou 的设计方式,仅支持 Mac ,开源免费,点击此处访问 MacDown 官网 - Ulysses一款由国外开发商 The Soulmen 制作的 Markdown 编辑器。与其它同类应用相比,Ulysses 最大的不同在于,它能根据内置的文件管理器,以及与 iCloud 云服务器的实时同步方案,达到最快捷的文章整理效率,支持OS X , iPad,26人民币每月,14天免费试用,点击此处访问 Ulysses 官网 - Byword一款轻量级的 Markdown 编辑器,支持Mac,iPhone和iPad,界面极简,功能强大,貌似要付费使用,点击此处 访问 Byword 官网 - MaHua一个在线编辑 Markdown 文档的编辑器,小众软件,VIM 快捷键支持,完美兼容 Github 的 Markdown 语法,界面稍许简陋,点击此处访问 MaHua - Dillinger来自国外的 Markdown 编辑器,漂亮强大,支持md、 html、pdf 文件导出,支持Dropbox、Github、Google Drive、Onedrive 一键保存,点击此处访问 Dillinger - CSDN中国专业IT社区CSDN (Chinese Software Developer Network) 创立于1999年,致力于为中国软件开发者提供知识传播、在线学习、职业发展等全生命周期服务。CSDN的在线编辑器功能强大,支持导出为HTML和md文件,注册账号后即可开始创作,点击此处访问CSDN官网 - 简书简书是一个优质的创作社区,你可以在线创作并发表到社区,是国内优质原创内容输出平台,简书从一开始就已经支持 Markdown 和富文本编辑,是一个为专门为作者打造的平台,点击此处访问简书官网 要细数 Markdown 编辑器的话,可能永远也数不尽,而且每个人的看法也不同,正所谓萝卜白菜各有所爱,什么编辑器不是最重要的,重要的是我们能写出优质的文章,不断学习进步!不断提升自我! 参考资料:《好用的Markdown编辑器一览》(By:月光)《10款流行的Markdown编辑器,总有一款适合你》(By:xiaoxiao_engineer)《解决作者们的焦虑:7 款优秀 Markdown 编辑工具推荐》(By:JailJT)","categories":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/categories/Markdown/"}],"tags":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/tags/Markdown/"},{"name":"编辑器","slug":"编辑器","permalink":"https://www.itrhx.com/tags/编辑器/"}]},{"title":"Hexo 博客主题个性化","slug":"A04-Hexo-blog-topic-personalization","date":"2018-08-27T13:25:24.452Z","updated":"2019-12-31T15:41:01.525Z","comments":true,"path":"2018/08/27/A04-Hexo-blog-topic-personalization/","link":"","permalink":"https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/","excerpt":"","text":"本文将讲述一些博客主题的美化、实用功能的添加,本文以作者 luuman 的 spfk 主题和作者 xaoxuu 的 Material X 主题为例,文章会不定时进行更新。文章涉及有关参考资料、教程、链接如有侵权请联系我删除! 本文在CSDN的链接:《Hexo 博客优化之博客美化》、《Hexo 博客优化之实用功能添加》,Hexo 博客专栏,从前期搭建到后期美化,帮您解决常见问题:《Github/Coding Pages + Hexo》,对您有帮助就点个赞吧❤️ 请注意:不同主题可能方法有些不同,相同主题不同版本,配置方法也有所差异! 博客美化前提条件:有一定的前端基础,了解 HTML、CSS、JS,了解 CSS 预处理语言 Sass、Less、Stylus,搞懂 hexo 的目录结构。 博客美化通用步骤:选定主题,认真阅读主题文档,分析主题目录结构,了解每个文件是对应网页哪个部分的,认真阅读美化教程,美化教程本质上只为你提供核心代码和思路,具体代码要添加到哪个地方,需要你自己搞懂主题结构,添加到需要的、合适的位置! 博客美化终极奥秘:创作第一,体验第二,避免繁杂,简洁为上! 【01】添加评论系统 主流的评论系统有很多,比如:网易云跟帖、多说、友言、畅言、来必力(LiveRe)、Disqus、Valine、Gitment等等,目前网易云跟帖、多说、友言都已经关闭了,还有些可能需要翻墙,比较麻烦,百度了一下,最后还是选择了来必力评论系统 进入来必力官网,注册一个账号(注册时可能需要翻墙) 注册完毕之后,登录,进入安装页面,选择 City 免费版安装,安装之后你会得到一段代码 我们打开主题文件下的 _config.yml 文件,添加如下代码: 在 \\themes\\hexo-theme-spfk\\layout\\_partial\\comments 文件夹下新建一个 livere.ejs 的文件,在里面填写来必力提供的代码: 123456789101112131415161718<!-- 来必力City版安装代码 --><div id=\"lv-container\" data-id=\"city\" data-uid=\"这里是你的uid\"> <script type=\"text/javascript\"> (function(d, s) { var j, e = d.getElementsByTagName(s)[0]; if (typeof LivereTower === 'function') { return; } j = d.createElement(s); j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; j.async = true; e.parentNode.insertBefore(j, e); })(document, 'script'); </script> <noscript>为正常使用来必力评论功能请激活JavaScript</noscript></div><!-- City版安装代码已完成 --> 打开 \\themes\\hexo-theme-spfk\\layout\\_partial\\article.ejs 文件,在适当位置添加如下红框中的代码: 完成以上操作之后,我们就可以使用来必力评论系统了 【02】添加卡通人物 我在逛别人博客的时候偶然发现右下角居然有一个萌萌的卡通人物,还能根据你鼠标位置摇头,瞬间被吸引到了,赶紧也给自己博客添加一个吧!点击此处进入该项目地址 输入如下命令获取 live2d : 1$ npm install --save hexo-helper-live2d 输入以下命令,下载相应的模型,将 packagename 更换成模型名称即可,更多模型选择请点击此处,各个模型的预览请访问原作者的博客 1$ npm install packagename 打开站点目录下的 _config.yml 文件,添加如下代码: 1234567891011live2d: enable: true scriptFrom: local model: use: live2d-widget-model-haruto #模型选择 display: position: right #模型位置 width: 150 #模型宽度 height: 300 #模型高度 mobile: show: false #是否在手机端显示 设置好过后我们就拥有了一个卡通人物 【03】自定义鼠标指针样式 在 \\themes\\material-x\\source\\less\\_base.less 文件 body 样式里写入如下代码: 123456body { cursor: url(https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur),auto; background-color: @theme_background; ...... ......} 鼠标指针可以用 Axialis CursorWorkshop 这个软件自己制作,不同主题具体放的文件有所不同,确保在博客主体 body 的 CSS 文件中即可,其中的鼠标指针链接可替换成自己的,首先尝试加载 https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur ,如果该文件不存在或由于其他原因无效,那么 auto 会被使用,也就是自动默认效果,图片格式为.ico、.ani、.cur,建议使用.cur,如果使用.ani或者其他格式无效,原因是浏览器兼容问题,请阅读参考文档或者参考以下兼容表: 浏览器 最低版本 格式 Internet Explorer 6.0 .cur / .ani Firefox (Gecko), Windows and Linux 1.5 (1.8) .cur / .png / .gif / .jpg Firefox (Gecko) 4.0 (2.0) .cur / .png / .gif / .jpg / .svg Opera — — Safari (Webkit) 3.0 (522-523) .cur / .png / .gif / .jpg 拓展阅读:《CSS 鼠标样式 cursor属性》 (By:歪脖先生的博客) 【04】添加鼠标点击爱心效果 在 \\themes\\hexo-theme-spfk\\source\\js 下新建文件 love.js,在 love.js 文件中添加以下代码: 1!function(e,t,a){function n(){c(\".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 500%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}\"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText=\"left:\"+d[e].x+\"px;top:\"+d[e].y+\"px;opacity:\"+d[e].alpha+\";transform:scale(\"+d[e].scale+\",\"+d[e].scale+\") rotate(45deg);background:\"+d[e].color+\";z-index:99999\");requestAnimationFrame(r)}function o(){var t=\"function\"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement(\"div\");a.className=\"heart\",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement(\"style\");a.type=\"text/css\";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName(\"head\")[0].appendChild(a)}function s(){return\"rgb(\"+~~(255*Math.random())+\",\"+~~(255*Math.random())+\",\"+~~(255*Math.random())+\")\"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document); 在 \\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件末尾添加以下代码: 12<!-- 页面点击小红心 --><script type=\"text/javascript\" src=\"/js/love.js\"></script> 完成以上操作后,当我们点击鼠标的时候就可以看见爱心的特效了 【05】添加鼠标点击显示字体效果 在 \\themes\\hexo-theme-spfk\\source\\js 下新建文件 click_show_text.js,在 click_show_text.js 文件中添加以下代码: 123456789101112131415161718192021222324252627282930313233var a_idx = 0;jQuery(document).ready(function($) { $(\"body\").click(function(e) { var a = new Array (\"富强\", \"民主\", \"文明\", \"和谐\", \"自由\", \"平等\", \"公正\", \"法治\", \"爱国\", \"敬业\", \"诚信\", \"友善\"); var $i = $(\"<span/>\").text(a[a_idx]); a_idx = (a_idx + 1) % a.length; var x = e.pageX, y = e.pageY; $i.css({ \"z-index\": 5, \"top\": y - 20, \"left\": x, \"position\": \"absolute\", \"font-weight\": \"bold\", \"color\": \"#FF0000\" }); $(\"body\").append($i); $i.animate({ \"top\": y - 180, \"opacity\": 0 }, 3000, function() { $i.remove(); }); }); setTimeout('delay()', 2000);});function delay() { $(\".buryit\").removeAttr(\"onclick\");} 其中的社会主义核心价值观可以根据你自己的创意替换为其他文字 如果想要每次点击显示的文字为不同颜色,可以将其中 color 值进行如下更改: 1\"color\": \"rgb(\" + ~~(255 * Math.random()) + \",\" + ~~(255 * Math.random()) + \",\" + ~~(255 * Math.random()) + \")\" 然后在 \\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件末尾添加以下代码: 12<!--单击显示文字--><script type=\"text/javascript\" src=\"/js/click_show_text.js\"></script> 最终实现效果如下: 【06】添加鼠标点击烟花爆炸效果 在 \\themes\\material-x\\source\\js 目录下新建一个 fireworks.js 的文件,里面写入以下代码: 1\"use strict\";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color=\"#F00\",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:\"linear\",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(\".fireworks\");if(canvasEl){var ctx=canvasEl.getContext(\"2d\"),numberOfParticules=30,pointerX=0,pointerY=0,tap=\"mousedown\",colors=[\"#FF1461\",\"#18FF92\",\"#5A87FF\",\"#FBF38C\"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+\"px\",canvasEl.style.height=window.innerHeight+\"px\",canvasEl.getContext(\"2d\").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){\"sidebar\"!==e.target.id&&\"toggle-sidebar\"!==e.target.id&&\"A\"!==e.target.nodeName&&\"IMG\"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener(\"resize\",setCanvasSize,!1)}\"use strict\";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color=\"#F00\",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:\"linear\",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(\".fireworks\");if(canvasEl){var ctx=canvasEl.getContext(\"2d\"),numberOfParticules=30,pointerX=0,pointerY=0,tap=\"mousedown\",colors=[\"#FF1461\",\"#18FF92\",\"#5A87FF\",\"#FBF38C\"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+\"px\",canvasEl.style.height=window.innerHeight+\"px\",canvasEl.getContext(\"2d\").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){\"sidebar\"!==e.target.id&&\"toggle-sidebar\"!==e.target.id&&\"A\"!==e.target.nodeName&&\"IMG\"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener(\"resize\",setCanvasSize,!1)}; 然后在 \\themes\\material-x\\layout\\layout.ejs 文件中写入以下代码: 123<canvas class=\"fireworks\" style=\"position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;\" ></canvas> <script type=\"text/javascript\" src=\"//cdn.bootcss.com/animejs/2.2.0/anime.min.js\"></script> <script type=\"text/javascript\" src=\"/js/fireworks.js\"></script> 最终效果: 【07】添加彩色滚动变换字体 在你想要添加彩色滚动变换字体的地方写入以下代码即可,其中文字可自行更改: 123456789101112131415161718192021222324252627282930313233343536373839404142<div id=\"binft\"></div> <script> var binft = function (r) { function t() { return b[Math.floor(Math.random() * b.length)] } function e() { return String.fromCharCode(94 * Math.random() + 33) } function n(r) { for (var n = document.createDocumentFragment(), i = 0; r > i; i++) { var l = document.createElement(\"span\"); l.textContent = e(), l.style.color = t(), n.appendChild(l) } return n } function i() { var t = o[c.skillI]; c.step ? c.step-- : (c.step = g, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : \"forward\" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = \"backward\", c.delay = a) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % o.length, c.direction = \"forward\")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(s, s + c.prefixP) : Math.min(s, t.length - c.skillP))), setTimeout(i, d) } var l = \"\", o = [\"青青陵上柏,磊磊涧中石。\", \"人生天地间,忽如远行客。\",\"斗酒相娱乐,聊厚不为薄。\", \"驱车策驽马,游戏宛与洛。\",\"洛中何郁郁,冠带自相索。\",\"长衢罗夹巷,王侯多第宅。\",\"两宫遥相望,双阙百余尺。\",\"极宴娱心意,戚戚何所迫?\"].map(function (r) { return r + \"\" }), a = 2, g = 1, s = 5, d = 75, b = [\"rgb(110,64,170)\", \"rgb(150,61,179)\", \"rgb(191,60,175)\", \"rgb(228,65,157)\", \"rgb(254,75,131)\", \"rgb(255,94,99)\", \"rgb(255,120,71)\", \"rgb(251,150,51)\", \"rgb(226,183,47)\", \"rgb(198,214,60)\", \"rgb(175,240,91)\", \"rgb(127,246,88)\", \"rgb(82,246,103)\", \"rgb(48,239,130)\", \"rgb(29,223,163)\", \"rgb(26,199,194)\", \"rgb(35,171,216)\", \"rgb(54,140,225)\", \"rgb(76,110,219)\", \"rgb(96,84,200)\"], c = { text: \"\", prefixP: -s, skillI: 0, skillP: 0, direction: \"forward\", delay: a, step: g }; i() }; binft(document.getElementById('binft')); </script> 最终效果: 【08】添加字数统计和阅读时长 先在博客目录下执行以下命令安装 hexo-wordcount 插件: 1$ npm i --save hexo-wordcount 注意:在 Material X 主题中,字数统计和阅读时长的功能我已提交 PR,在最新版本中,只需要安装插件后,在主题 config.yml 配置文件里,将 word_count 关键字设置为 true 即可,对于旧版本,可以通过以下方法实现: 以 Material X 主题(版本 1.2.1)为例,在 \\themes\\material-x\\layout\\_meta 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码: 123456789101112131415161718192021<% if(isPostList || !isPostList){ %> <% if (theme.word_count && !post.no_word_count) { %> <div style=\"margin-right: 10px;\"> <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-keyboard\"></i> <span class=\"post-meta-item-text\"> 字数统计: </span> <span class=\"post-count\"><%= wordcount(post.content) %>字</span> </span> </span> &nbsp; | &nbsp; <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-hourglass-half\"></i> <span class=\"post-meta-item-text\"> 阅读时长≈</span> <span class=\"post-count\"><%= min2read(post.content) %>分</span> </span> </span> </div> <% } %><% } %> 然后在主题的配置文件 _config.yml 找到 meta 关键字,将 word 填入 header 中: 123meta: header: [title, author, date, categories, tags, counter, word, top] footer: [updated, share] 最后在主题目录下的 _config.yml 添加以下配置即可 1word_count: true 效果图: 同样的,以 spfk 主题为例,在 \\themes\\hexo-theme-spfk\\layout\\_partial\\post 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码: 1234567891011121314151617<div style=\"margin-top:10px;\"> <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-keyboard-o\"></i> <span class=\"post-meta-item-text\"> 字数统计: </span> <span class=\"post-count\"><%= wordcount(post.content) %>字</span> </span> </span> &nbsp; | &nbsp; <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-hourglass-half\"></i> <span class=\"post-meta-item-text\"> 阅读时长: </span> <span class=\"post-count\"><%= min2read(post.content) %>分</span> </span> </span></div> 然后在 \\themes\\hexo-theme-spfk\\layout\\_partial\\article.ejs 中适当位置添加以下代码: 最后在主题目录下的 _config.yml 添加以下配置 1word_count: true 如果显示的位置不好,可以自行更改其位置,成功配置后的效果如下: 另外:要在博客底部显示所有文章的总字数,可以点击此处,根据你博客底部文件的类型选择相应的代码放在适当的位置即可,前提是要安装好 hexo-wordcount 插件,例如我使用 Material X 主题,在 \\themes\\material-x\\layout\\_partial 目录下的 footer.ejs 文件中添加如下代码: 12<i class=\"fas fa-chart-area\"></i><span class=\"post-count\">字数统计:<%= totalcount(site) %></span> 实现效果如下: 【09】添加背景音乐 打开网页版网易云音乐,选择你准备添加的背景音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,复制底下的HTML代码 然后将此代码放到你想要放的地方,比如放在博客的左侧,则打开 \\themes\\hexo-theme-spfk\\layout\\_partial\\left-col.ejs 文件,将复制的HTML代码粘贴进去,再进行适当的位置设置让播放器更美观,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐 最后效果如下: 这种网易云音乐外链的方式有很多局限性,因此推荐使用aplayer,GitHub地址为:https://github.com/MoePlayer/APlayer ,参考教程:《hexo上的aplayer应用》 【10】添加网站运行时间 一个比较好的小功能,可以看见自己的博客运行多久了,时间一天天的增加,成就感也会一天天增加的在 \\themes\\hexo-theme-spfk\\layout\\_partial\\footer.ejs 文件下添加以下代码: 1234567891011121314151617<span id=\"timeDate\">载入天数...</span><span id=\"times\">载入时分秒...</span><script> var now = new Date(); function createtime() { var grt= new Date(\"08/10/2018 17:38:00\");//在此处修改你的建站时间,格式:月/日/年 时:分:秒 now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours); if(String(hnum).length ==1 ){hnum = \"0\" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum); mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = \"0\" + mnum;} seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum); snum = Math.round(seconds); if(String(snum).length ==1 ){snum = \"0\" + snum;} document.getElementById(\"timeDate\").innerHTML = \"本站已安全运行 \"+dnum+\" 天 \"; document.getElementById(\"times\").innerHTML = hnum + \" 小时 \" + mnum + \" 分 \" + snum + \" 秒\"; } setInterval(\"createtime()\",250);</script> 最后效果如下: 【11】添加百度统计 百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中 访问百度统计首页,注册一个账号后登陆,添加你的博客网站 接着点击代码获取,复制该代码 然后到目录 \\Hexo\\themes\\hexo-theme-spfk\\layout\\_partial 下新建一个 baidu-analytics.ejs 文件,里面粘贴你刚刚复制的代码 修改主题文件夹下的 _config.yml 文件,将你的key(图中涂掉部分)填写进去: 所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据 另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统 【12】浏览器网页标题恶搞当用户访问你的博客时点击到了其他网页,我们可以恶搞一下网页标题,呼唤用户回来,首先在目录 \\themes\\material-x\\source\\js 下新建一个 FunnyTitle.js 文件,在里面填写如下代码: 1234567891011121314151617// 浏览器搞笑标题var OriginTitle = document.title;var titleTime;document.addEventListener('visibilitychange', function () { if (document.hidden) { $('[rel=\"icon\"]').attr('href', \"/funny.ico\"); document.title = '╭(°A°`)╮ 页面崩溃啦 ~'; clearTimeout(titleTime); } else { $('[rel=\"icon\"]').attr('href', \"/favicon.ico\"); document.title = '(ฅ>ω<*ฅ) 噫又好啦 ~' + OriginTitle; titleTime = setTimeout(function () { document.title = OriginTitle; }, 2000); }}); 其中 funny.ico 是用户切换到其他标签后你网站的图标,favicon.ico 是正常图标,然后在 \\themes\\material-x\\layout\\layout.ejs 文件中添加如下代码: 12<!--浏览器搞笑标题--><script type=\"text/javascript\" src=\"/js/FunnyTitle.js\"></script> 再次部署博客后就可以看见标题搞笑的效果了: 【13】背景添加动态线条效果 在 \\Hexo\\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件中添加如下代码: 1234<!--动态线条背景--><script type=\"text/javascript\"color=\"220,220,220\" opacity='0.7' zIndex=\"-2\" count=\"200\" src=\"//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js\"></script> 其中: color:表示线条颜色,三个数字分别为(R,G,B),默认:(0,0,0) opacity:表示线条透明度(0~1),默认:0.5 count:表示线条的总数量,默认:150 zIndex:表示背景的z-index属性,css属性用于控制所在层的位置,默认:-1 最终实现效果: 【14】添加人体时钟 无意中发现了个有趣的人体时钟 HONE HONE CLOCK,作者是个日本人,点击此处访问作者博客,点击此处在作者原博客上查看动态样式,点击此处查看动态大图,如果你的博客上有合适的地方,加上一个人体时钟会很有趣的 实现代码: 12345<!--人体时钟背景透明--><script charset=\"Shift_JIS\" src=\"http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_tr.js\"></script><!--人体时钟背景白--><script charset=\"Shift_JIS\" src=\"http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_wh.js\"></script> 其他网页小挂件推荐: http://abowman.com/ 里面有很多有趣的小挂件,可以养养鱼、龟、狗、仓鼠等各式各样的虚拟宠物,能根据你的鼠标指针位置移动,直接复制代码就可以用 http://www.revolvermaps.com/ 它提供网站访客地理信息,可以以2D、3D等形式显示 http://www.amazingcounters.com/ 免费网站计数器,有非常多的样式供你选择,可以设置计数器初始数值,可以设置按访问量计数,也可以按独立访问者计数 https://www.seniverse.com/widget/get 心知天气提供基于Web的免费天气插件,可以为你的网站添加一项简洁美观的天气预报功能,并自动适配PC和手机上的浏览 【15】添加RSS订阅 RSS订阅是站点用来和其他站点之间共享内容的一种简易方式,即Really Simple Syndication(简易信息聚合),如果不会使用,可以参见百度百科:https://baike.baidu.com/item/RSS%E8%AE%A2%E9%98%85/663114 ;首先我们安装feed插件,在本地hexo目录下右键git bash here,输入以下命令: 1$ npm install hexo-generator-feed 等待安装完成后,打开hexo目录下配置文件的_config.yml,在末尾添加以下配置: 12345678910# Extensions## Plugins: http://hexo.io/plugins/#RSS订阅plugin:- hexo-generator-feed#Feed Atomfeed:type: atompath: atom.xmllimit: 20 随后打开主题配置文件_config.yml,添加以下配置: 1rss: /atom.xml 至此,RSS订阅功能添加完成 【16】添加网站雪花飘落效果 样式一和样式二分别如下: 实现方法:在 \\Hexo\\themes\\hexo-theme-spfk\\source\\js 目录下新建一个 snow.js 文件,粘贴以下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142/*样式一*/(function($){ $.fn.snow = function(options){ var $flake = $('<div id=\"snowbox\" />').css({'position': 'absolute','z-index':'9999', 'top': '-50px'}).html('&#10052;'), documentHeight = $(document).height(), documentWidth = $(document).width(), defaults = { minSize : 10, maxSize : 20, newOn : 1000, flakeColor : \"#AFDAEF\" /* 此处可以定义雪花颜色,若要白色可以改为#FFFFFF */ }, options = $.extend({}, defaults, options); var interval= setInterval( function(){ var startPositionLeft = Math.random() * documentWidth - 100, startOpacity = 0.5 + Math.random(), sizeFlake = options.minSize + Math.random() * options.maxSize, endPositionTop = documentHeight - 200, endPositionLeft = startPositionLeft - 500 + Math.random() * 500, durationFall = documentHeight * 10 + Math.random() * 5000; $flake.clone().appendTo('body').css({ left: startPositionLeft, opacity: startOpacity, 'font-size': sizeFlake, color: options.flakeColor }).animate({ top: endPositionTop, left: endPositionLeft, opacity: 0.2 },durationFall,'linear',function(){ $(this).remove() }); }, options.newOn); };})(jQuery);$(function(){ $.fn.snow({ minSize: 5, /* 定义雪花最小尺寸 */ maxSize: 50,/* 定义雪花最大尺寸 */ newOn: 300 /* 定义密集程度,数字越小越密集 */ });}); 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128/*样式二*//* 控制下雪 */function snowFall(snow) { /* 可配置属性 */ snow = snow || {}; this.maxFlake = snow.maxFlake || 200; /* 最多片数 */ this.flakeSize = snow.flakeSize || 10; /* 雪花形状 */ this.fallSpeed = snow.fallSpeed || 1; /* 坠落速度 */}/* 兼容写法 */requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function(callback) { setTimeout(callback, 1000 / 60); };cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame;/* 开始下雪 */snowFall.prototype.start = function(){ /* 创建画布 */ snowCanvas.apply(this); /* 创建雪花形状 */ createFlakes.apply(this); /* 画雪 */ drawSnow.apply(this)}/* 创建画布 */function snowCanvas() { /* 添加Dom结点 */ var snowcanvas = document.createElement(\"canvas\"); snowcanvas.id = \"snowfall\"; snowcanvas.width = window.innerWidth; snowcanvas.height = document.body.clientHeight; snowcanvas.setAttribute(\"style\", \"position:absolute; top: 0; left: 0; z-index: 1; pointer-events: none;\"); document.getElementsByTagName(\"body\")[0].appendChild(snowcanvas); this.canvas = snowcanvas; this.ctx = snowcanvas.getContext(\"2d\"); /* 窗口大小改变的处理 */ window.onresize = function() { snowcanvas.width = window.innerWidth; /* snowcanvas.height = window.innerHeight */ }}/* 雪运动对象 */function flakeMove(canvasWidth, canvasHeight, flakeSize, fallSpeed) { this.x = Math.floor(Math.random() * canvasWidth); /* x坐标 */ this.y = Math.floor(Math.random() * canvasHeight); /* y坐标 */ this.size = Math.random() * flakeSize + 2; /* 形状 */ this.maxSize = flakeSize; /* 最大形状 */ this.speed = Math.random() * 1 + fallSpeed; /* 坠落速度 */ this.fallSpeed = fallSpeed; /* 坠落速度 */ this.velY = this.speed; /* Y方向速度 */ this.velX = 0; /* X方向速度 */ this.stepSize = Math.random() / 30; /* 步长 */ this.step = 0 /* 步数 */}flakeMove.prototype.update = function() { var x = this.x, y = this.y; /* 左右摆动(余弦) */ this.velX *= 0.98; if (this.velY <= this.speed) { this.velY = this.speed } this.velX += Math.cos(this.step += .05) * this.stepSize; this.y += this.velY; this.x += this.velX; /* 飞出边界的处理 */ if (this.x >= canvas.width || this.x <= 0 || this.y >= canvas.height || this.y <= 0) { this.reset(canvas.width, canvas.height) }};/* 飞出边界-放置最顶端继续坠落 */flakeMove.prototype.reset = function(width, height) { this.x = Math.floor(Math.random() * width); this.y = 0; this.size = Math.random() * this.maxSize + 2; this.speed = Math.random() * 1 + this.fallSpeed; this.velY = this.speed; this.velX = 0;};// 渲染雪花-随机形状(此处可修改雪花颜色!!!)flakeMove.prototype.render = function(ctx) { var snowFlake = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size); snowFlake.addColorStop(0, \"rgba(255, 255, 255, 0.9)\"); /* 此处是雪花颜色,默认是白色 */ snowFlake.addColorStop(.5, \"rgba(255, 255, 255, 0.5)\"); /* 若要改为其他颜色,请自行查 */ snowFlake.addColorStop(1, \"rgba(255, 255, 255, 0)\"); /* 找16进制的RGB 颜色代码。 */ ctx.save(); ctx.fillStyle = snowFlake; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore();};/* 创建雪花-定义形状 */function createFlakes() { var maxFlake = this.maxFlake, flakes = this.flakes = [], canvas = this.canvas; for (var i = 0; i < maxFlake; i++) { flakes.push(new flakeMove(canvas.width, canvas.height, this.flakeSize, this.fallSpeed)) }}/* 画雪 */function drawSnow() { var maxFlake = this.maxFlake, flakes = this.flakes; ctx = this.ctx, canvas = this.canvas, that = this; /* 清空雪花 */ ctx.clearRect(0, 0, canvas.width, canvas.height); for (var e = 0; e < maxFlake; e++) { flakes[e].update(); flakes[e].render(ctx); } /* 一帧一帧的画 */ this.loop = requestAnimationFrame(function() { drawSnow.apply(that); });}/* 调用及控制方法 */var snow = new snowFall({maxFlake:60});snow.start(); 然后在 \\Hexo\\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件里引用即可: 12<!-- 雪花特效 --><script type=\"text/javascript\" src=\"\\js\\snow.js\"></script> 如果没效果,请确认网页是否已载入JQurey,如果没有请在下雪代码之前引入JQ即可: 12<script type=\"text/javascript\" src=\"http://libs.baidu.com/jquery/1.8.3/jquery.js\"></script><script type=\"text/javascript\" src=\"http://libs.baidu.com/jquery/1.8.3/jquery.min.js\"></script> 原文链接:《分享两种圣诞节雪花特效JS代码(网站下雪效果)》 【17】添加 Fork me on GitHub 效果 效果图: 点击此处可以查看更多样式,将相应样式的代码复制到你想要放的地方就OK了,代码里的链接也要替换成你的,更多创意,比如 Follow me on CSDN ,只需要用PS改掉图片里的文字,替换掉相应链接即可 【18】添加背景动态彩带效果 样式一是鼠标点击后彩带自动更换样式,样式二是飘动的彩带: 实现方法:在 \\themes\\material-x\\layout\\layout.ejs 文件的body前面添加如下代码: 12<!-- 样式一(鼠标点击更换样式) --><script src=\"https://g.joyinshare.com/hc/ribbon.min.js\" type=\"text/javascript\"></script> 12<!-- 样式二(飘动的彩带) --><script src=\"https://g.joyinshare.com/hc/piao.js\" type=\"text/javascript\"></script> 【19】添加背景代码雨特效 新建 DigitalRain.js,写入以下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657window.onload = function(){ //获取画布对象 var canvas = document.getElementById(\"canvas\"); //获取画布的上下文 var context =canvas.getContext(\"2d\"); var s = window.screen; var W = canvas.width = s.width; var H = canvas.height; //获取浏览器屏幕的宽度和高度 //var W = window.innerWidth; //var H = window.innerHeight; //设置canvas的宽度和高度 canvas.width = W; canvas.height = H; //每个文字的字体大小 var fontSize = 12; //计算列 var colunms = Math.floor(W /fontSize); //记录每列文字的y轴坐标 var drops = []; //给每一个文字初始化一个起始点的位置 for(var i=0;i<colunms;i++){ drops.push(0); } //运动的文字 var str =\"WELCOME TO WWW.ITRHX.COM\"; //4:fillText(str,x,y);原理就是去更改y的坐标位置 //绘画的函数 function draw(){ context.fillStyle = \"rgba(238,238,238,.08)\";//遮盖层 context.fillRect(0,0,W,H); //给字体设置样式 context.font = \"600 \"+fontSize+\"px Georgia\"; //给字体添加颜色 context.fillStyle = [\"#33B5E5\", \"#0099CC\", \"#AA66CC\", \"#9933CC\", \"#99CC00\", \"#669900\", \"#FFBB33\", \"#FF8800\", \"#FF4444\", \"#CC0000\"][parseInt(Math.random() * 10)];//randColor();可以rgb,hsl, 标准色,十六进制颜色 //写入画布中 for(var i=0;i<colunms;i++){ var index = Math.floor(Math.random() * str.length); var x = i*fontSize; var y = drops[i] *fontSize; context.fillText(str[index],x,y); //如果要改变时间,肯定就是改变每次他的起点 if(y >= canvas.height && Math.random() > 0.99){ drops[i] = 0; } drops[i]++; } }; function randColor(){//随机颜色 var r = Math.floor(Math.random() * 256); var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); return \"rgb(\"+r+\",\"+g+\",\"+b+\")\"; } draw(); setInterval(draw,35);}; 在主题文件的相关css文件中(以 Material X 1.2.1 主题为例,在\\themes\\material-x-1.2.1\\source\\less\\_main.less 文件末尾)添加以下代码: 12345678910canvas { position: fixed; right: 0px; bottom: 0px; min-width: 100%; min-height: 100%; height: auto; width: auto; z-index: -1;} 然后在主题的 layout.ejs 文件中引入即可: 123<!-- 数字雨 --><canvas id=\"canvas\" width=\"1440\" height=\"900\" ></canvas><script type=\"text/javascript\" src=\"/js/DigitalRain.js\"></script> 最终效果: 代码来源:http://www.lxl8800.cn/Main/Resource 【20】自定义一个不使用主题模板渲染的独立页面     有时候我们需要新建一个独立的页面,这个页面不使用主题的渲染,具有自己独立的样式,可以放一些自己的作品,相册什么的,以下就介绍这种独立页面的实现方法。 方法一:     使用 Hexo 提供的跳过渲染配置,在博客根目录的配置文件 _config.yml 里找到 skip_render 关键字,在后面添加想要跳过渲染的页面,比如我们创建 \\source\\about\\index.html, 配置文件填写:skip_render: about\\**,那么就表示 \\source\\about 里所有的文件将跳过渲染,里面的文件将会被直接复制到 public 文件夹,此时就会得到一个独立的 about 页面;官方文档:https://hexo.io/docs/configuration 方法二:     在文章头部的 Front-matter 里添加配置 layout: false 来跳过渲染配置,比如我们要使 about 页面跳过渲染,创建 \\source\\about\\index.md,将这个页面的相关 HTML 代码写进.md文件并保存,然后在 index.md 的头部写入: 123456789---layout: false---{% raw %}这里是 HTML 代码{% endraw %} PS:Front-matter 是 .md 文件最上方以 — 分隔的区域,用于指定个别文件的变量,官方文档:https://hexo.io/docs/front-matter 效果可以对比我的博客主页和关于页面 【21】更改本地预览端口号hexo博客在执行 hexo s 进行本地预览的时候,默认端口号是4000,当该端口号被占用时会报错 Error: listen EADDRINUSE 0.0.0.0:4000 ,此时可以关闭占用该端口的进程,也可以更换端口号,更换端口号可以通过以下两种方法实现: 方法一:在根目录的 _config.yml 配置文件内加上如下代码更改 hexo s 运行时的端口号: 1234server: port: 5000 compress: true header: true 方法二:通过 hexo server -p 5000 命令来指定端口,这种方法只是本次执行有效 未完待续……","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"主题个性化","slug":"主题个性化","permalink":"https://www.itrhx.com/tags/主题个性化/"},{"name":"Material X","slug":"Material-X","permalink":"https://www.itrhx.com/tags/Material-X/"},{"name":"spfk","slug":"spfk","permalink":"https://www.itrhx.com/tags/spfk/"}]},{"title":"Markdown 语法&技巧总结","slug":"A03-markdown","date":"2018-08-25T09:57:16.879Z","updated":"2019-09-09T13:40:22.061Z","comments":true,"path":"2018/08/25/A03-markdown/","link":"","permalink":"https://www.itrhx.com/2018/08/25/A03-markdown/","excerpt":"","text":"在写博客的时候,我们不希望都是千篇一律的没有色彩,多了解一些 Markdown 语法技巧有利于丰富我们的博客,看起来更有 feel ! – 插入图片 如果你使用 MarkdownPad 的话就比较方便,可以直接选择插入本地图片或者是网络图片,实质是通过以下代码实现的,小括号里面就是你的图片地址,中括号里面是图片的替代文字,比如上面的图片代码如下:1![车](https://cdn.jsdelivr.net/gh/TRHX/ImageHosting/ITRHX-PIC/A03/01.jpg) – 插入音乐 打开网页版网易云音乐,选择你准备插入的音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,选择好尺寸后,复制底下的HTML代码 然后将此HTML代码粘贴到你想要放的地方,可自行调节播放器的大小,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐,比如See You Again (中英文版) - 罗艺恒这首歌曲代码如下: 1<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"//music.163.com/outchain/player?type=2&id=32405683&auto=1&height=66\"></iframe> – 插入视频 高考毕业了我们为下一届的学弟学妹们录制高考加油视频,我担任后期制作,在这里就以该视频为例٩(๑❛ᴗ❛๑)۶,在腾讯视频播放页面找到分享按钮,复制该视频的通用代码(其他视频播放平台也一样),粘贴到文章中对应位置即可,可根据情况调整视频播放器的大小 1<iframe frameborder=\"0\" width=\"840\" height=\"500\" src=\"https://v.qq.com/txp/iframe/player.html?vid=x0643zvgtf7\" allowFullScreen=\"true\"></iframe> 未完待续……","categories":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/categories/Markdown/"}],"tags":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/tags/Markdown/"},{"name":"技巧","slug":"技巧","permalink":"https://www.itrhx.com/tags/技巧/"}]},{"title":"使用 Github Pages 和 Hexo 搭建自己的独立博客","slug":"A02-hexo-blog","date":"2018-08-15T13:34:58.325Z","updated":"2019-12-29T07:21:04.675Z","comments":true,"path":"2018/08/15/A02-hexo-blog/","link":"","permalink":"https://www.itrhx.com/2018/08/15/A02-hexo-blog/","excerpt":"","text":"– 前言 首先感谢您能访问我的博客:TRHX’S BLOG 这是一篇有关如何使用 Github Pages 和 Hexo 搭建属于自己独立博客的详尽教程,本人是软件工程专业本科生,目前只学习了C和C++编程语言,对网站开发的有关知识几乎为零,这也是我搭建好自己的博客之后写的第一篇博客,刚开始搭建博客的时候自己也是网上各种百度,由于自己属于小白那种,历经了千辛万苦才弄好,所以借这个机会写一篇小白真正能看懂的博客搭建教程,教你一步一步走向成功的彼岸! 推荐文章: 《我为什么写博客》 (By 知明所以) 《为什么你应该(从现在开始就)写博客》 (By 刘未鹏 | Mind Hacks) – 入门 Github Pages Github Pages可以被认为是用户编写的、托管在github上的静态网页。使用Github Pages可以为你提供一个免费的服务器,免去了自己搭建服务器和写数据库的麻烦。此外还可以绑定自己的域名。 Hexo Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 – 安装 Node.js点击此处访问官网,按需下载相应版本,默认安装可以了 注:本人在安装过程中出现了Warning 1909,无法创建快捷方式,这种情况很少出现,如果在安装过程中也有这种情况请参考百度文库(win10系统实测可行):《Win7安装程序警告1909无法创建快捷方式》 – 安装 Git点击此处访问官网,按需下载相应版本,默认安装即可参考资料:《如何在windows下安装GIT》 (By 俊雨廷休) 《Pro Git(中文版)》 – 检验软件是否安装成功同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI Here 和 Git Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行123$ git --version$ node -v$ npm -v – Hexo 安装选择一个磁盘,新建一个文件夹,自己重命名文件夹(如:我的文件夹为:E\\TRHX_Blog),博客相关文件将储存在此文件夹下,在该文件夹下右键鼠标,点击 Git Bash Here,输入以下 npm 命令即可安装,第一个命令表示安装 hexo,第二个命令表示安装 hexo 部署到 git page 的 deployer,如图所示即为安装成功12$ npm install hexo-cli -g$ npm install hexo-deployer-git --save – Hexo 初始化配置在刚才新建的文件夹里面再次新建一个 Hexo 文件夹(如:我的文件夹为:E\\TRHX_Blog\\Hexo),进入该 Hexo 文件夹右键鼠标,点击 Git Bash Here,输入以下命令,如图所示则安装成功1$ hexo init Hexo 安装完成后,将会在指定文件夹中新建所需要的文件,Hexo 文件夹下的目录如下: – 本地查看效果执行以下命令,执行完即可登录 http://localhost:4000/ 查看效果12$ hexo generate$ hexo server 显示以下信息说明操作成功:1INFO Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop. 登录 http://localhost:4000/ 查看效果: – 将博客部署到 Github Pages 上到目前为止,我们的本地博客就成功搭建了,但是现在我们只能通过本地连接查看博客,我们要做的是让其他人也能够访问我们的博客,这就需要我们将博客部署到Github Pages上 一、注册 Github 账户:点击此处访问 Github 官网,点击 Sign Up 注册账户 二、创建项目代码库:点击 New repository 开始创建,步骤及注意事项见图: 三、配置 SSH 密钥:只有配置好 SSH 密钥后,我们才可以通过 git 操作实现本地代码库与 Github 代码库同步,在你第一次新建的文件夹里面(如:我的文件夹为:E\\TRHX_Blog) Git Bash Here 输入以下命令:12$ ssh-keygen -t rsa -C "your email@example.com"//引号里面填写你的邮箱地址,比如我的是tanrenhou@126.com 之后会出现:123Generating public/private rsa key pair.Enter file in which to save the key (/c/Users/you/.ssh/id_rsa)://到这里可以直接回车将密钥按默认文件进行存储 然后会出现:123Enter passphrase (empty for no passphrase)://这里是要你输入密码,其实不需要输什么密码,直接回车就行Enter same passphrase again: 接下来屏幕会显示:123456Your identification has been saved in /c/Users/you/.ssh/id_rsa.Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.The key fingerprint is:这里是各种字母数字组成的字符串,结尾是你的邮箱The key's randomart image is:这里也是各种字母数字符号组成的字符串 运行以下命令,将公钥的内容复制到系统粘贴板上1$ clip < ~/.ssh/id_rsa.pub 四、在 GitHub 账户中添加你的公钥 1.登陆 GitHub,进入 Settings: 2.点击 SSH and GPG Keys: 3.选择 New SSH key: 4.粘贴密钥: 五、测试 输入以下命令:注意:git@github.com不要做任何更改!1$ ssh -T git@github.com 之后会显示: 输入 yes 后会显示:此时表示设置正确 六、配置 Git 个人信息 Git 会根据用户的名字和邮箱来记录提交,GitHub 也是用这些信息来做权限的处理,输入以下命令进行个人信息的设置,把名称和邮箱替换成你自己的,名字可以不是 GitHub 的昵称,但为了方便记忆,建议与 GitHub 一致12$ git config --global user.name "此处填你的用户名"$ git config --global user.email "此处填你的邮箱" 到此为止 SSH Key 配置成功,本机已成功连接到 Github – 将本地的 Hexo 文件更新到 Github 的库中一、登录 Github 打开自己的项目 yourname.github.io 二、鼠标移到 Clone or download 按钮,选择 Use SSH 三、一键复制地址 四、打开你创建的 Hexo 文件夹(如:E:\\TRHX_Blog\\Hexo),右键用记事本(或者Notepad++、Vs Code等)打开该文件夹下的 _config.yml 文件 五、按下图修改 _config.yml 文件并保存 六、在 Hexo 文件夹下分别执行以下命令12$ hexo g$ hexo d 或者直接执行1$ hexo g -d 执行完之后会让你输入你的 Github 的账号和密码,如果此时报以下错误,说明你的 deployer 没有安装成功1ERROR Deployer not found: git 需要执行以下命令再安装一次:1npm install hexo-deployer-git --save 再执行 hexo g -d,你的博客就会部署到 Github 上了 七、访问博客 你的博客地址:https://你的用户名.github.io,比如我的是:https://trhx.github.io ,现在每个人都可以通过此链接访问你的博客了 – 如何在博客上发表文章博客已经成功搭建了,但是我们该怎么写博客呢? 一、新建一个空文章,输入以下命令,会在项目 \\Hexo\\source\\_posts 中生成 文章标题.md 文件,文章标题根据需要命名1$ hexo n "文章标题" 也可以直接在 \\Hexo\\source\\_posts 目录下右键鼠标新建文本文档,改后缀为 .md 即可,这种方法比较方便 二、用编辑器编写文章 md 全称 Markdown, Markdown 是 2004 年由 John Gruberis 设计和开发的纯文本格式的语法,非常的简单实用,常用的标记符号屈指可数,几分钟即可学会, .md 文件可以使用支持 Markdown 语法的编辑器编辑,然后将写好的文章(.md文件)保存到 \\Hexo\\source\\_posts 文件夹下即可推荐 Windows 上使用 MarkdownPad2 或者 小书匠 编辑器,macOS 上使用 Mou 编辑器,Linux 上使用 Remarkable 编辑器,Web 端上使用 简书 ,另外可以参考我的另一篇文章:《主流 Markdown 编辑器推荐》当我们用编辑器写好文章后,可以使用以下命令将其推送到服务器上12$ hexo g$ hexo d或者将两个命令合二为一输入以下命令:1$ hexo d -g现在访问你的博客就可以看见写好的文章啦!参考资料:《10款流行的Markdown编辑器》 (By xiaoxiao_engineer) 《献给写作者的 Markdown 新手指南》 (By 简书) 《认识与入门 Markdown》 (By Te_Lee) 《markdown简明语法》 (By 不如) 《markdown基本语法》 (By 高鸿祥) 《Markdown 公式指导手册》 (By Harries)# – 如何为博客更换自己喜欢的主题 博客也搭建好了,文章也会写了,但是!!!默认的主题并不喜欢怎么办?现在,我们就来为自己的博客更换自己喜欢的主题 点击此处进入 Hexo 官网的主题专栏,我们可以看见有许多的主题供我们选择 我们要做的就是把主题克隆过来,在此我们以主题 Aero-Dual 为例,点进去我们就可以看见该主题作者的博客,鼠标滑到底,我们可以看见 Theme By Levblanc 的字样(其他主题类似),点击作者 Levblanc ,页面就会跳转到该主题所有的相关文件在 Github 上的地址,复制该地址 再打开 Hexo 文件夹下的 themes 目录(如:E:\\TRHX_Blog\\Hexo\\themes),右键 Git Bash Here,输入以下命令:1$ git clone 此处填写你刚才复制的主题地址 比如要安装 Aero-Dual 主题,则输入命令:1$ git clone https://github.com/levblanc/hexo-theme-aero-dual 等待下载完成后即可在 themes 目录下生成 hexo-theme-aero-dual 文件夹,然后打开 Hexo 文件夹下的配置文件 _config.yml ,找到关键字 theme,修改参数为:theme:hexo-theme-aero-dual (其他主题修改成相应名称即可),再次注意冒号后面有一个空格! 返回 Hexo 目录,右键 Git Bash Here ,输入以下命令开始部署主题:12$ hexo g $ hexo s 此时打开浏览器,访问 http://localhost:4000/ 就可看见我们的主题已经更换了,如果感觉效果满意,我们就可以把它部署到Github上了 打开 Hexo 文件夹,右键 Git Bash Here ,输入以下命令:123$ hexo clean //该命令的作用是清除缓存,若不输入此命令,服务器有可能更新不了主题$ hexo g -d 此时访问自己的博客即可看见更换后的主题,但我们仍然需要对主题的相关配置进行修改,比如网站标题,图标等等,Hexo 中有两份主要的配置文件,名称都是 _config.yml ,它们均是用于站点配置使用的。其中,一份位于站点根目录下(比如我的:E:\\TRHX_Blog\\Hexo\\_config.yml),主要包含 Hexo 本身整站的配置;另一份位于主题目录下(比如我的:E:\\TRHX_Blog\\Hexo\\themes\\hexo-theme-aero-dual\\_config.yml),这份配置由主题作者提供,主要用于配置主题相关的选项,一般 _config.yml 文件里都有相关注释,按需修改即可 参考资料:《有哪些好看的 Hexo 主题?》 (知乎) 《Hexo | 配置》 (Hexo官方文档) 《hexo常用命令笔记》 (By 小弟调调) – 为你的 Hexo 博客配置个性域名本人在配置域名的时候问题百出,百度的各种方法都不管用,打开网站总是 404,可能是我太笨了 o(╥﹏╥)o ,不过好在后来终于解决了这个问题 首先我们要购买域名,阿里云,腾讯云都可以,也不贵,一年几十块钱,最便宜几块钱也能买到,以阿里云为例,我购买的域名是 itrhx.com,购买过程就不赘述了,选择阿里云的解析平台,来到阿里云的管理控制台,点击进入域名解析列表或者直接点击域名后面的解析 方法一:点击添加记录,需要添加两个记录,两个记录类型都是 CNAME ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都是填你自己的博客地址(比如我的是:trhx.github.io),保存之后域名解析就完成了!方法二:两个记录类型为 A ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都为博客的 IP 地址,IP 地址可以 cmd 中输入 ping 你的博客地址 获得(比如我的:ping trhx.github.io),保存之后域名解析就完成了!有关解析记录类型的区别可以参考《域名解析中A记录、CNAME、MX记录、NS记录的区别和联系》 为了使 GitHub 接收我们的域名,还需要在博客的根目录下添加一个名为 CNAME 的文件(注意不要加.txt,没有任何后缀名!),这个文件放到 Hexo 文件夹的 source 里面,(比如我的是:E:\\TRHX_Blog\\Hexo\\source),文件里面填写你的域名(加不加www都行),比如要填写我的域名,文件里面就写:www.itrhx.com 或者 itrhx.com,经过以上操作,别人就可以通过 www.itrhx.com 、itrhx.com 、trhx.github.io 三个当中任意一个访问我的博客了!你的也一样! 有关加不加www的问题有以下区别: 如果你填写的是没有www的,比如 itrhx.com,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 https://itrhx.com 如果你填写的是带www的,比如 www.itrhx.com ,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 http://www.itrhx.com 如果你在其他平台购买域名,或者选择 DNSPod 等其他域名解析,操作方法大同小异,遇到问题可自行百度解决! 参考资料:《推荐几家域名注册服务商》 (By Jelly Bool) 《盘点十大免费DNS域名解析服务:稳定、可靠》 – 结语一顿操作下来虽然有点儿累,但看见拥有了自己的博客还是非常有成就感的,人生就是需要折腾,那么现在就开始你的创作之旅吧!文章的不断积累,你会从中受益很多的!另外,这是一篇小白写的适用于小白的博客搭建教程,比较详细,有这方面基础的可以百度有简略一点儿的教程,文中如有错误还请大佬指出改正!文中涉及参考资料如有侵权请联系我删除!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Github Pages","slug":"Github-Pages","permalink":"https://www.itrhx.com/tags/Github-Pages/"},{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"}]},{"title":"Hello World!","slug":"A01-hello-world","date":"2018-08-10T09:38:00.000Z","updated":"2019-09-09T14:13:26.239Z","comments":true,"path":"2018/08/10/A01-hello-world/","link":"","permalink":"https://www.itrhx.com/2018/08/10/A01-hello-world/","excerpt":"","text":"人类的幸福和欢乐在于奋斗,而最有价值的是为理想而奋斗! ——— 苏格拉底 Human happiness and joy lie in struggle, and what is most valuable is striving for ideals! ——— Socrates","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/tags/BLOG/"}]}]} \ No newline at end of file +{"meta":{"title":"TRHX'S BLOG","subtitle":"一入 IT 深似海 从此学习无绝期","description":"TRHX 的个人博客;主攻 Python、爬虫、WEB前端、大数据、数据分析、数据可视化;求知若饥,虚心若愚,一入 IT 深似海,从此学习无绝期,记录毕生所学!","author":"TRHX","url":"https://www.itrhx.com"},"pages":[{"title":"","date":"2019-09-23T02:54:05.384Z","updated":"2019-09-23T02:54:05.384Z","comments":true,"path":"404.html","permalink":"https://www.itrhx.com/404.html","excerpt":"","text":"404 页面 | TRHX'S BLOG *{padding:0;margin:0}body{background-color:#eee;background-image:url(\"https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.9/images/background/021.webp\");}#catch-the-cat{width:100%;margin-top:32px;text-align:center}#Copyright{text-align:center;font-size:15px;color:#000;margin:30px}#Copyright a{color:#000}#Copyright a:hover{color:#f00} 404 Not Found! 很抱歉,您访问的页面不存在,可能是输入地址有误或该地址已被删除! window.game = new CatchTheCatGame({ w: 11, h: 11, r: 20, backgroundColor: 0xffffff, parent: 'catch-the-cat', statusBarAlign: 'center', credit: 'www.itrhx.com' }); Copyright ©2018-2019 TRHX'S BLOG       鄂ICP备19003281号-3     点击返回首页"},{"title":"","date":"2020-02-28T07:25:47.215Z","updated":"2020-02-28T07:25:47.215Z","comments":true,"path":"2019-nCoV/index.html","permalink":"https://www.itrhx.com/2019-nCoV/index.html","excerpt":"","text":"全国新型冠状病毒实时分布图 body { height: 88vh; margin: 0; padding: 0; overflow: hidden; } h2 { text-align:center; margin: 20px 0 0 0; color: #0822B5; } h3 { text-align:center; margin: 20px 0 0 0; color: #0822B5; } a { color: #178b50; text-decoration: none; } a:hover { color: #d81d1b; text-decoration: none; } iframe { overflow:hidden; margin: 20px 0 0 0; border: none; } TRHX'S BLOG丨新冠肺炎实时疫情图(数据来源:新浪新闻) 相关链接:武汉新型冠状病毒防疫信息收集平台 丨 2019-nCoV 疫情信息导航网站"},{"title":"所有分类","date":"2019-04-27T16:28:11.064Z","updated":"2019-04-27T16:28:11.064Z","comments":true,"path":"categories/index.html","permalink":"https://www.itrhx.com/categories/index.html","excerpt":"","text":""},{"title":"小伙伴们","date":"2020-03-14T07:55:33.386Z","updated":"2020-03-14T07:55:33.386Z","comments":false,"path":"friends/index.html","permalink":"https://www.itrhx.com/friends/index.html","excerpt":"","text":"名称:TRHX’S BLOG主页:https://www.itrhx.com/头像:https://www.itrhx.com/images/trhx.png标签:Python、爬虫、前端简介:求知若饥,虚心若愚! 由于目前友链数过多,所以暂停交换友链,敬请见谅!"},{"title":"所有标签","date":"2019-05-05T16:01:26.324Z","updated":"2019-05-05T16:01:26.324Z","comments":true,"path":"tags/index.html","permalink":"https://www.itrhx.com/tags/index.html","excerpt":"","text":""},{"title":"留言","date":"2020-02-27T11:40:10.042Z","updated":"2020-02-27T11:40:10.042Z","comments":true,"path":"comments/index.html","permalink":"https://www.itrhx.com/comments/index.html","excerpt":"","text":"                                    采用 Gitalk 评论系统,需使用 GitHub 账号登录,请尽情灌水吧!😉由于 Gitalk 调用的是 GitHub 的 issues,如果您参与了评论,可能别人评论您也会收到邮件,若不想再收到邮件,可以到 issues 页面取消订阅,取消后,如果您再次评论,也不会再收到邮件提醒!"},{"title":"","date":"2020-03-02T05:42:14.094Z","updated":"2020-03-02T05:42:14.094Z","comments":true,"path":"about/index.html","permalink":"https://www.itrhx.com/about/index.html","excerpt":"","text":"TRHX'S BLOG | ABOUT document.onkeydown = function () { if (window.event && window.event.keyCode == 123) { event.keyCode = 0; event.returnValue = false; return false; } }; 您的浏览器不支持audio标签,无法播放音乐! 江湖名称:TRHX 常驻之地:China~丨湖北丨武汉 初度之辰:1999 擅长领域:Python丨爬虫丨前端 技能丨Skill HTML/CSS/JS 65% C/C++ 20% JAVA 30% PYTHON 80% HEXO/GIT 90% PS/PR/AE 65% 简介丨Introduction ● 学历:武汉某本科,软件工程专业大三学生; ● 现况:自学 Python 中,网络爬虫方向; ● 目标:优秀前端工程师 or 网络爬虫工程师; ● 博客:好记性不如烂笔头,记录学习过程; ● 兴趣:酷爱编程,业余公路自行车手,WOT 玩家; ● 其他:虽然很菜,但是在努力学习中! 联系我丨Contact me Copyright © 2018-2020 TRHX'S BLOG. All rights reserved. if ('addEventListener' in window) { window.addEventListener('load', function () { document.body.className = document.body.className.replace(/\\bis-loading\\b/, ''); }); document.body.className += (navigator.userAgent.match(/(MSIE|rv:11\\.0)/) ? ' is-ie' : ''); } uniform mat4 uProjection; uniform mat4 uModelview; uniform vec3 uResolution; uniform vec3 uOffset; uniform vec3 uDOF; //x:focus distance, y:focus radius, z:max radius uniform vec3 uFade; //x:start distance, y:half distance, z:near fade start attribute vec3 aPosition; attribute vec3 aEuler; attribute vec2 aMisc; //x:size, y:fade varying vec3 pposition; varying float psize; varying float palpha; varying float pdist; //varying mat3 rotMat; varying vec3 normX; varying vec3 normY; varying vec3 normZ; varying vec3 normal; varying float diffuse; varying float specular; varying float rstop; varying float distancefade; void main(void) { // Projection is based on vertical angle vec4 pos = uModelview * vec4(aPosition + uOffset, 1.0); gl_Position = uProjection * pos; gl_PointSize = aMisc.x * uProjection[1][1] / -pos.z * uResolution.y * 0.5; pposition = pos.xyz; psize = aMisc.x; pdist = length(pos.xyz); palpha = smoothstep(0.0, 1.0, (pdist - 0.1) / uFade.z); vec3 elrsn = sin(aEuler); vec3 elrcs = cos(aEuler); mat3 rotx = mat3( 1.0, 0.0, 0.0, 0.0, elrcs.x, elrsn.x, 0.0, -elrsn.x, elrcs.x ); mat3 roty = mat3( elrcs.y, 0.0, -elrsn.y, 0.0, 1.0, 0.0, elrsn.y, 0.0, elrcs.y ); mat3 rotz = mat3( elrcs.z, elrsn.z, 0.0, -elrsn.z, elrcs.z, 0.0, 0.0, 0.0, 1.0 ); mat3 rotmat = rotx * roty * rotz; normal = rotmat[2]; mat3 trrotm = mat3( rotmat[0][0], rotmat[1][0], rotmat[2][0], rotmat[0][1], rotmat[1][1], rotmat[2][1], rotmat[0][2], rotmat[1][2], rotmat[2][2] ); normX = trrotm[0]; normY = trrotm[1]; normZ = trrotm[2]; const vec3 lit = vec3(0.6917144638660746, 0.6917144638660746, -0.20751433915982237); float tmpdfs = dot(lit, normal); if(tmpdfs < 0.0) { normal = -normal; tmpdfs = dot(lit, normal); } diffuse = 0.4 + tmpdfs; vec3 eyev = normalize(-pos.xyz); if(dot(eyev, normal) > 0.0) { vec3 hv = normalize(eyev + lit); specular = pow(max(dot(hv, normal), 0.0), 20.0); } else { specular = 0.0; } rstop = clamp((abs(pdist - uDOF.x) - uDOF.y) / uDOF.z, 0.0, 1.0); rstop = pow(rstop, 0.5); //-0.69315 = ln(0.5) distancefade = min(1.0, exp((uFade.x - pdist) * 0.69315 / uFade.y)); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform vec3 uDOF; //x:focus distance, y:focus radius, z:max radius uniform vec3 uFade; //x:start distance, y:half distance, z:near fade start const vec3 fadeCol = vec3(0.08, 0.03, 0.06); varying vec3 pposition; varying float psize; varying float palpha; varying float pdist; //varying mat3 rotMat; varying vec3 normX; varying vec3 normY; varying vec3 normZ; varying vec3 normal; varying float diffuse; varying float specular; varying float rstop; varying float distancefade; float ellipse(vec2 p, vec2 o, vec2 r) { vec2 lp = (p - o) / r; return length(lp) - 1.0; } void main(void) { vec3 p = vec3(gl_PointCoord - vec2(0.5, 0.5), 0.0) * 2.0; vec3 d = vec3(0.0, 0.0, -1.0); float nd = normZ.z; //dot(-normZ, d); if(abs(nd) < 0.0001) discard; float np = dot(normZ, p); vec3 tp = p + d * np / nd; vec2 coord = vec2(dot(normX, tp), dot(normY, tp)); //angle = 15 degree const float flwrsn = 0.258819045102521; const float flwrcs = 0.965925826289068; mat2 flwrm = mat2(flwrcs, -flwrsn, flwrsn, flwrcs); vec2 flwrp = vec2(abs(coord.x), coord.y) * flwrm; float r; if(flwrp.x < 0.0) { r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.36, 0.96) * 0.5); } else { r = ellipse(flwrp, vec2(0.065, 0.024) * 0.5, vec2(0.58, 0.96) * 0.5); } if(r > rstop) discard; vec3 col = mix(vec3(1.0, 0.8, 0.75), vec3(1.0, 0.9, 0.87), r); float grady = mix(0.0, 1.0, pow(coord.y * 0.5 + 0.5, 0.35)); col *= vec3(1.0, grady, grady); col *= mix(0.8, 1.0, pow(abs(coord.x), 0.3)); col = col * diffuse + specular; col = mix(fadeCol, col, distancefade); float alpha = (rstop > 0.001)? (0.5 - r / (rstop * 2.0)) : 1.0; alpha = smoothstep(0.0, 1.0, alpha) * palpha; gl_FragColor = vec4(col * 0.5, alpha); } uniform vec3 uResolution; attribute vec2 aPosition; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); texCoord = aPosition.xy * 0.5 + vec2(0.5, 0.5); screenCoord = aPosition.xy * vec2(uResolution.z, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform vec2 uTimes; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec3 col; float c; vec2 tmpv = texCoord * vec2(0.8, 1.0) - vec2(0.95, 1.0); c = exp(-pow(length(tmpv) * 1.8, 2.0)); col = mix(vec3(0.02, 0.0, 0.03), vec3(0.96, 0.98, 1.0) * 1.5, c); gl_FragColor = vec4(col * 0.5, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 col = texture2D(uSrc, texCoord); gl_FragColor = vec4(col.rgb * 2.0 - vec3(0.5), 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; uniform vec4 uBlurDir; //dir(x, y), stride(z, w) varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 col = texture2D(uSrc, texCoord); col = col + texture2D(uSrc, texCoord + uBlurDir.xy * uDelta); col = col + texture2D(uSrc, texCoord - uBlurDir.xy * uDelta); col = col + texture2D(uSrc, texCoord + (uBlurDir.xy + uBlurDir.zw) * uDelta); col = col + texture2D(uSrc, texCoord - (uBlurDir.xy + uBlurDir.zw) * uDelta); gl_FragColor = col / 5.0; } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_FragColor = texture2D(uSrc, texCoord); } uniform vec3 uResolution; attribute vec2 aPosition; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { gl_Position = vec4(aPosition, 0.0, 1.0); texCoord = aPosition.xy * 0.5 + vec2(0.5, 0.5); screenCoord = aPosition.xy * vec2(uResolution.z, 1.0); } #ifdef GL_ES //precision mediump float; precision highp float; #endif uniform sampler2D uSrc; uniform sampler2D uBloom; uniform vec2 uDelta; varying vec2 texCoord; varying vec2 screenCoord; void main(void) { vec4 srccol = texture2D(uSrc, texCoord) * 2.0; vec4 bloomcol = texture2D(uBloom, texCoord); vec4 col; col = srccol + bloomcol * (vec4(1.0) + srccol); col *= smoothstep(1.0, 0.0, pow(length((texCoord - vec2(0.5)) * 2.0), 1.2) * 0.5); col = pow(col, vec4(0.45454545454545)); //(1.0 / 2.2) gl_FragColor = vec4(col.rgb, 1.0); gl_FragColor.a = 1.0; }"},{"title":"","date":"2019-12-29T06:55:50.753Z","updated":"2019-12-29T06:55:50.753Z","comments":true,"path":"games/PacMan/index.html","permalink":"https://www.itrhx.com/games/PacMan/index.html","excerpt":"","text":"吃豆人 | TRHX'S BLOG body{background-color: #000} *{padding:0;margin:0;} .wrapper{ width: 960px; margin:0 auto; line-height:36px; text-align:center; color:#999; } canvas{display:block;background: #000;} .mod-botton{ height: 32px; padding: 15px 0; text-align: center; } #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(255, 255, 255, 0.5);font-style:normal} #footer a{color:rgba(255, 255, 255, 0.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 不支持画布 【按空格键开始、暂停或继续游戏,方向键移动吃豆人】 Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"box/about/index.html","permalink":"https://www.itrhx.com/box/about/index.html","excerpt":"","text":"关于本页丨TRHX'S BLOG 关于本页 本页面收集了比较常用或者实用的网站,相当于一个小小的导航页面。 整个页面由 Viggo 开发,完全开源,如果你也喜欢,欢迎去其 Github 点亮 star。 关于 Viggo Designer. Viggo. Full-time UI designer with an enduring interest in Coding. 一个全职的用户界面设计师,优秀的前端开发工程师,擅长 WEB 开发、WEB 设计、UI/UX 设计,对编程,拍照和单车有着持久的兴趣,生活在广州;如果您想招收此方面的人才,Viggo 无疑是一个很好的选择。 关于 TRHX TRHX 在校本科软件工程大三学生,主攻 Python、爬虫和前端。 一个想成为大佬的在校本科生,倾向于 Python、网络爬虫、数据分析、数据可视化、WEB 前端方面的学习,热爱编程、单车和户外运动,坚信好记性不如烂笔头,业精于勤荒于嬉,行成于思毁于随。 COPYRIGHT 2018 - 2020 WEBSTACK 丨 DESIGNED BY VIGGO 丨 CHANGED BY TRHX"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/2048/index.html","permalink":"https://www.itrhx.com/games/2048/index.html","excerpt":"","text":"2048 | TRHX'S BLOG 2048 使用方向键操作 New Game score:0 GAME OVER Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/element/index.html","permalink":"https://www.itrhx.com/games/element/index.html","excerpt":"","text":"3D元素周期表 | TRHX'S BLOG html, body { height: 100%; } body { background-color: #000000; margin: 0; font-family: Helvetica, sans-serif;; overflow: hidden; } a { color: #ffffff; } #info { position: absolute; width: 100%; color: #ffffff; padding: 5px; font-family: Monospace; font-size: 13px; font-weight: bold; text-align: center; z-index: 1; } #menu { position: absolute; bottom: 20px; width: 100%; text-align: center; font-family: verdana,Tahoma,Arial,Hei,\"Microsoft Yahei\",SimHei; } .element { width: 120px; height: 160px; box-shadow: 0px 0px 12px rgba(0,255,255,0.5); border: 1px solid rgba(127,255,255,0.25); text-align: center; cursor: default; } .element:hover { box-shadow: 0px 0px 12px rgba(0,255,255,0.75); border: 1px solid rgba(127,255,255,0.75); } .element .number { position: absolute; top: 20px; right: 20px; font-size: 12px; color: rgba(127,255,255,0.75); } .element .symbol { position: absolute; top: 40px; left: 0px; right: 0px; font-size: 60px; font-weight: bold; color: rgba(255,255,255,0.75); text-shadow: 0 0 10px rgba(0,255,255,0.95); } .element .details { position: absolute; bottom: 15px; left: 0px; right: 0px; font-size: 12px; color: rgba(127,255,255,0.75); } button { color: rgba(127,255,255,0.75); background: transparent; outline: 1px solid rgba(127,255,255,0.75); border: 0px; padding: 5px 10px; cursor: pointer; } button:hover { background-color: rgba(0,255,255,0.5); } button:active { color: #000000; background-color: rgba(0,255,255,0.75); } 表面 球体 螺旋 网格 var table = [ \"H\", \"Hydrogen\", \"1.00794\", 1, 1, \"He\", \"Helium\", \"4.002602\", 18, 1, \"Li\", \"Lithium\", \"#6.941\", 1, 2, \"Be\", \"Beryllium\", \"9.012182\", 2, 2, \"B\", \"Boron\", \"#10.811\", 13, 2, \"C\", \"Carbon\", \"#12.0107\", 14, 2, \"N\", \"Nitrogen\", \"#14.0067\", 15, 2, \"O\", \"Oxygen\", \"#15.9994\", 16, 2, \"F\", \"Fluorine\", \"18.9984032\", 17, 2, \"Ne\", \"Neon\", \"#20.1797\", 18, 2, \"Na\", \"Sodium\", \"22.98976...\", 1, 3, \"Mg\", \"Magnesium\", \"#24.305\", 2, 3, \"Al\", \"Aluminium\", \"26.9815386\", 13, 3, \"Si\", \"Silicon\", \"#28.0855\", 14, 3, \"P\", \"Phosphorus\", \"30.973762\", 15, 3, \"S\", \"Sulfur\", \"#32.065\", 16, 3, \"Cl\", \"Chlorine\", \"#35.453\", 17, 3, \"Ar\", \"Argon\", \"#39.948\", 18, 3, \"K\", \"Potassium\", \"#39.948\", 1, 4, \"Ca\", \"Calcium\", \"#40.078\", 2, 4, \"Sc\", \"Scandium\", \"44.955912\", 3, 4, \"Ti\", \"Titanium\", \"#47.867\", 4, 4, \"V\", \"Vanadium\", \"#50.9415\", 5, 4, \"Cr\", \"Chromium\", \"#51.9961\", 6, 4, \"Mn\", \"Manganese\", \"54.938045\", 7, 4, \"Fe\", \"Iron\", \"#55.845\", 8, 4, \"Co\", \"Cobalt\", \"58.933195\", 9, 4, \"Ni\", \"Nickel\", \"#58.6934\", 10, 4, \"Cu\", \"Copper\", \"#63.546\", 11, 4, \"Zn\", \"Zinc\", \"#65.38\", 12, 4, \"Ga\", \"Gallium\", \"#69.723\", 13, 4, \"Ge\", \"Germanium\", \"#72.63\", 14, 4, \"As\", \"Arsenic\", \"#74.9216\", 15, 4, \"Se\", \"Selenium\", \"#78.96\", 16, 4, \"Br\", \"Bromine\", \"#79.904\", 17, 4, \"Kr\", \"Krypton\", \"#83.798\", 18, 4, \"Rb\", \"Rubidium\", \"#85.4678\", 1, 5, \"Sr\", \"Strontium\", \"#87.62\", 2, 5, \"Y\", \"Yttrium\", \"88.90585\", 3, 5, \"Zr\", \"Zirconium\", \"#91.224\", 4, 5, \"Nb\", \"Niobium\", \"92.90628\", 5, 5, \"Mo\", \"Molybdenum\", \"#95.96\", 6, 5, \"Tc\", \"Technetium\", \"(98)\", 7, 5, \"Ru\", \"Ruthenium\", \"#101.07\", 8, 5, \"Rh\", \"Rhodium\", \"#102.9055\", 9, 5, \"Pd\", \"Palladium\", \"#106.42\", 10, 5, \"Ag\", \"Silver\", \"#107.8682\", 11, 5, \"Cd\", \"Cadmium\", \"#112.411\", 12, 5, \"In\", \"Indium\", \"#114.818\", 13, 5, \"Sn\", \"Tin\", \"#118.71\", 14, 5, \"Sb\", \"Antimony\", \"#121.76\", 15, 5, \"Te\", \"Tellurium\", \"127.6\", 16, 5, \"I\", \"Iodine\", \"126.90447\", 17, 5, \"Xe\", \"Xenon\", \"#131.293\", 18, 5, \"Cs\", \"Caesium\", \"#132.9054\", 1, 6, \"Ba\", \"Barium\", \"#132.9054\", 2, 6, \"La\", \"Lanthanum\", \"138.90547\", 4, 9, \"Ce\", \"Cerium\", \"#140.116\", 5, 9, \"Pr\", \"Praseodymium\", \"140.90765\", 6, 9, \"Nd\", \"Neodymium\", \"#144.242\", 7, 9, \"Pm\", \"Promethium\", \"(145)\", 8, 9, \"Sm\", \"Samarium\", \"#150.36\", 9, 9, \"Eu\", \"Europium\", \"#151.964\", 10, 9, \"Gd\", \"Gadolinium\", \"#157.25\", 11, 9, \"Tb\", \"Terbium\", \"158.92535\", 12, 9, \"Dy\", \"Dysprosium\", \"162.5\", 13, 9, \"Ho\", \"Holmium\", \"164.93032\", 14, 9, \"Er\", \"Erbium\", \"#167.259\", 15, 9, \"Tm\", \"Thulium\", \"168.93421\", 16, 9, \"Yb\", \"Ytterbium\", \"#173.054\", 17, 9, \"Lu\", \"Lutetium\", \"#174.9668\", 18, 9, \"Hf\", \"Hafnium\", \"#178.49\", 4, 6, \"Ta\", \"Tantalum\", \"180.94788\", 5, 6, \"W\", \"Tungsten\", \"#183.84\", 6, 6, \"Re\", \"Rhenium\", \"#186.207\", 7, 6, \"Os\", \"Osmium\", \"#190.23\", 8, 6, \"Ir\", \"Iridium\", \"#192.217\", 9, 6, \"Pt\", \"Platinum\", \"#195.084\", 10, 6, \"Au\", \"Gold\", \"196.966569\", 11, 6, \"Hg\", \"Mercury\", \"#200.59\", 12, 6, \"Tl\", \"Thallium\", \"#204.3833\", 13, 6, \"Pb\", \"Lead\", \"207.2\", 14, 6, \"Bi\", \"Bismuth\", \"#208.9804\", 15, 6, \"Po\", \"Polonium\", \"(209)\", 16, 6, \"At\", \"Astatine\", \"(210)\", 17, 6, \"Rn\", \"Radon\", \"(222)\", 18, 6, \"Fr\", \"Francium\", \"(223)\", 1, 7, \"Ra\", \"Radium\", \"(226)\", 2, 7, \"Ac\", \"Actinium\", \"(227)\", 4, 10, \"Th\", \"Thorium\", \"232.03806\", 5, 10, \"Pa\", \"Protactinium\", \"#231.0588\", 6, 10, \"U\", \"Uranium\", \"238.02891\", 7, 10, \"Np\", \"Neptunium\", \"(237)\", 8, 10, \"Pu\", \"Plutonium\", \"(244)\", 9, 10, \"Am\", \"Americium\", \"(243)\", 10, 10, \"Cm\", \"Curium\", \"(247)\", 11, 10, \"Bk\", \"Berkelium\", \"(247)\", 12, 10, \"Cf\", \"Californium\", \"(251)\", 13, 10, \"Es\", \"Einstenium\", \"(252)\", 14, 10, \"Fm\", \"Fermium\", \"(257)\", 15, 10, \"Md\", \"Mendelevium\", \"(258)\", 16, 10, \"No\", \"Nobelium\", \"(259)\", 17, 10, \"Lr\", \"Lawrencium\", \"(262)\", 18, 10, \"Rf\", \"Rutherfordium\", \"(267)\", 4, 7, \"Db\", \"Dubnium\", \"(268)\", 5, 7, \"Sg\", \"Seaborgium\", \"(271)\", 6, 7, \"Bh\", \"Bohrium\", \"(272)\", 7, 7, \"Hs\", \"Hassium\", \"(270)\", 8, 7, \"Mt\", \"Meitnerium\", \"(276)\", 9, 7, \"Ds\", \"Darmstadium\", \"(281)\", 10, 7, \"Rg\", \"Roentgenium\", \"(280)\", 11, 7, \"Cn\", \"Copernicium\", \"(285)\", 12, 7, \"Uut\", \"Unutrium\", \"(284)\", 13, 7, \"Fl\", \"Flerovium\", \"(289)\", 14, 7, \"Uup\", \"Ununpentium\", \"(288)\", 15, 7, \"Lv\", \"Livermorium\", \"(293)\", 16, 7, \"Uus\", \"Ununseptium\", \"(294)\", 17, 7, \"Uuo\", \"Ununoctium\", \"(294)\", 18, 7 ]; var camera, scene, renderer; var controls; var objects = []; var targets = { table: [], sphere: [], helix: [], grid: [] }; init(); animate(); function init() { camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 10000 ); camera.position.z = 3000; scene = new THREE.Scene(); // table for ( var i = 0; i < table.length; i += 5 ) { var element = document.createElement( 'div' ); element.className = 'element'; element.style.backgroundColor = 'rgba(0,127,127,' + ( Math.random() * 0.5 + 0.25 ) + ')'; var number = document.createElement( 'div' ); number.className = 'number'; number.textContent = (i/5) + 1; element.appendChild( number ); var symbol = document.createElement( 'div' ); symbol.className = 'symbol'; symbol.textContent = table[ i ]; element.appendChild( symbol ); var details = document.createElement( 'div' ); details.className = 'details'; details.innerHTML = table[ i + 1 ] + '' + table[ i + 2 ]; element.appendChild( details ); var object = new THREE.CSS3DObject( element ); object.position.x = Math.random() * 4000 - 2000; object.position.y = Math.random() * 4000 - 2000; object.position.z = Math.random() * 4000 - 2000; scene.add( object ); objects.push( object ); // var object = new THREE.Object3D(); object.position.x = ( table[ i + 3 ] * 140 ) - 1330; object.position.y = - ( table[ i + 4 ] * 180 ) + 990; targets.table.push( object ); } // sphere var vector = new THREE.Vector3(); for ( var i = 0, l = objects.length; i < l; i ++ ) { var phi = Math.acos( -1 + ( 2 * i ) / l ); var theta = Math.sqrt( l * Math.PI ) * phi; var object = new THREE.Object3D(); object.position.x = 800 * Math.cos( theta ) * Math.sin( phi ); object.position.y = 800 * Math.sin( theta ) * Math.sin( phi ); object.position.z = 800 * Math.cos( phi ); vector.copy( object.position ).multiplyScalar( 2 ); object.lookAt( vector ); targets.sphere.push( object ); } // helix var vector = new THREE.Vector3(); for ( var i = 0, l = objects.length; i < l; i ++ ) { var phi = i * 0.175 + Math.PI; var object = new THREE.Object3D(); object.position.x = 900 * Math.sin( phi ); object.position.y = - ( i * 8 ) + 450; object.position.z = 900 * Math.cos( phi ); vector.x = object.position.x * 2; vector.y = object.position.y; vector.z = object.position.z * 2; object.lookAt( vector ); targets.helix.push( object ); } // grid for ( var i = 0; i < objects.length; i ++ ) { var object = new THREE.Object3D(); object.position.x = ( ( i % 5 ) * 400 ) - 800; object.position.y = ( - ( Math.floor( i / 5 ) % 5 ) * 400 ) + 800; object.position.z = ( Math.floor( i / 25 ) ) * 1000 - 2000; targets.grid.push( object ); } // renderer = new THREE.CSS3DRenderer(); renderer.setSize( window.innerWidth, window.innerHeight ); renderer.domElement.style.position = 'absolute'; document.getElementById( 'container' ).appendChild( renderer.domElement ); // controls = new THREE.TrackballControls( camera, renderer.domElement ); controls.rotateSpeed = 0.5; controls.minDistance = 500; controls.maxDistance = 6000; controls.addEventListener( 'change', render ); var button = document.getElementById( 'table' ); button.addEventListener( 'click', function ( event ) { transform( targets.table, 2000 ); }, false ); var button = document.getElementById( 'sphere' ); button.addEventListener( 'click', function ( event ) { transform( targets.sphere, 2000 ); }, false ); var button = document.getElementById( 'helix' ); button.addEventListener( 'click', function ( event ) { transform( targets.helix, 2000 ); }, false ); var button = document.getElementById( 'grid' ); button.addEventListener( 'click', function ( event ) { transform( targets.grid, 2000 ); }, false ); transform( targets.table, 5000 ); // window.addEventListener( 'resize', onWindowResize, false ); } function transform( targets, duration ) { TWEEN.removeAll(); for ( var i = 0; i < objects.length; i ++ ) { var object = objects[ i ]; var target = targets[ i ]; new TWEEN.Tween( object.position ) .to( { x: target.position.x, y: target.position.y, z: target.position.z }, Math.random() * duration + duration ) .easing( TWEEN.Easing.Exponential.InOut ) .start(); new TWEEN.Tween( object.rotation ) .to( { x: target.rotation.x, y: target.rotation.y, z: target.rotation.z }, Math.random() * duration + duration ) .easing( TWEEN.Easing.Exponential.InOut ) .start(); } new TWEEN.Tween( this ) .to( {}, duration * 2 ) .onUpdate( render ) .start(); } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize( window.innerWidth, window.innerHeight ); render(); } function animate() { requestAnimationFrame( animate ); TWEEN.update(); controls.update(); } function render() { renderer.render( scene, camera ); } document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.751Z","updated":"2019-12-29T06:55:50.751Z","comments":true,"path":"games/cat/index.html","permalink":"https://www.itrhx.com/games/cat/index.html","excerpt":"","text":"圈小猫 | TRHX'S BLOG body {background-color: #eeeeee} #catch-the-cat {width: 100%;text-align: center;} #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(0,0,0,.5);font-style:normal} #footer a{color:rgba(0,0,0,.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 游戏:《圈小猫》 window.game = new CatchTheCatGame({ w: 11, h: 11, r: 20, backgroundColor: 0xffffff, parent: 'catch-the-cat', statusBarAlign: 'center', credit: 'www.itrhx.com' }); Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.754Z","updated":"2019-12-29T06:55:50.754Z","comments":true,"path":"games/piano/index.html","permalink":"https://www.itrhx.com/games/piano/index.html","excerpt":"","text":"网页版钢琴 | TRHX'S BLOG 网页版钢琴 qaz sx dc rfv gb hn jm ik, w e t y u 弹奏方法 使用鼠标左键点击钢琴键,或者键入钢琴键上输入的键盘字母。 Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2019-12-29T06:55:50.754Z","updated":"2019-12-29T06:55:50.754Z","comments":true,"path":"games/gobang/index.html","permalink":"https://www.itrhx.com/games/gobang/index.html","excerpt":"","text":"五子棋 | TRHX'S BLOG canvas { display: block; margin: 60px auto; box-shadow: -2px -2px 2px #efefef, 5px 5px 5px #b9b9b9; cursor: pointer; } .btn-wrap { display: flex; flex-direction: row; justify-content: center; } .btn-wrap div { margin: 0 10px; } div>span { display: inline-block; padding: 10px 20px; color: #fff; background-color: #6496ED; border-radius: 5px; cursor: pointer; } div.unable span { background: #D6D6D4; color: #adacaa; } #result-wrap { text-align: center; margin:50px 0 0 0; } #footer{position:relative;clear:both;padding:10px 20px 40px 0;padding:10px 0;width:100%;text-align:center}#footer address{display:inline-block;padding:2px 10px;color:rgba(0,0,0,.5);font-style:normal} #footer a{color:rgba(0,0,0,.5);cursor:grab}#footer a:hover{border-bottom:1px dotted #00387d;color:#00387d} 人机五子棋对弈 重新开始 悔棋 撤销悔棋 var over = false; var me = true; //我 var _nowi = 0, _nowj = 0; //记录自己下棋的坐标 var _compi = 0, _compj = 0; //记录计算机当前下棋的坐标 var _myWin = [], _compWin = []; //记录我,计算机赢的情况 var backAble = false, returnAble = false; var resultTxt = document.getElementById('result-wrap'); var chressBord = []; //棋盘 for (var i = 0; i < 15; i++) { chressBord[i] = []; for (var j = 0; j < 15; j++) { chressBord[i][j] = 0; } } //赢法的统计数组 var myWin = []; var computerWin = []; //赢法数组 var wins = []; for (var i = 0; i < 15; i++) { wins[i] = []; for (var j = 0; j < 15; j++) { wins[i][j] = []; } } var count = 0; //赢法总数 //横线赢法 for (var i = 0; i < 15; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[i][j + k][count] = true; } count++; } } //竖线赢法 for (var i = 0; i < 15; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[j + k][i][count] = true; } count++; } } //正斜线赢法 for (var i = 0; i < 11; i++) { for (var j = 0; j < 11; j++) { for (var k = 0; k < 5; k++) { wins[i + k][j + k][count] = true; } count++; } } //反斜线赢法 for (var i = 0; i < 11; i++) { for (var j = 14; j > 3; j--) { for (var k = 0; k < 5; k++) { wins[i + k][j - k][count] = true; } count++; } } // debugger; for (var i = 0; i < count; i++) { myWin[i] = 0; _myWin[i] = 0; computerWin[i] = 0; _compWin[i] = 0; } var chess = document.getElementById(\"chess\"); var context = chess.getContext('2d'); context.strokeStyle = '#bfbfbf'; //边框颜色 var backbtn = document.getElementById(\"goback\"); var returnbtn = document.getElementById(\"return\"); window.onload = function () { drawChessBoard(); // 画棋盘 } document.getElementById(\"restart\").onclick = function () { window.location.reload(); } // 我,下棋 chess.onclick = function (e) { if (over) { return; } if (!me) { return; } // 悔棋功能可用 backbtn.className = backbtn.className.replace(new RegExp(\"(\\\\s|^)unable(\\\\s|$)\"), \" \"); var x = e.offsetX; var y = e.offsetY; var i = Math.floor(x / 30); var j = Math.floor(y / 30); _nowi = i; _nowj = j; if (chressBord[i][j] == 0) { oneStep(i, j, me); chressBord[i][j] = 1; //我,已占位置 for (var k = 0; k < count; k++) { // 将可能赢的情况都加1 if (wins[i][j][k]) { // debugger; myWin[k]++; _compWin[k] = computerWin[k]; computerWin[k] = 6; //这个位置对方不可能赢了 if (myWin[k] == 5) { // window.alert('你赢了'); resultTxt.innerHTML = '恭喜,你赢了!'; over = true; } } } if (!over) { me = !me; computerAI(); } } } // 悔棋 backbtn.onclick = function (e) { if (!backAble) { return; } over = false; me = true; // resultTxt.innerHTML = 'emmmm,悔棋中'; // 撤销悔棋功能可用 returnbtn.className = returnbtn.className.replace(new RegExp(\"(\\\\s|^)unable(\\\\s|$)\"), \" \"); // 我,悔棋 chressBord[_nowi][_nowj] = 0; //我,已占位置 还原 minusStep(_nowi, _nowj); //销毁棋子 for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_nowi][_nowj][k]) { myWin[k]--; computerWin[k] = _compWin[k]; //这个位置对方可能赢 } }// 计算机相应的悔棋 chressBord[_compi][_compj] = 0; //计算机,已占位置 还原 minusStep(_compi, _compj); //销毁棋子 for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_compi][_compj][k]) { computerWin[k]--; myWin[k] = _myWin[i]; //这个位置对方可能赢 } } resultTxt.innerHTML = '--人机五子棋--'; returnAble = true; backAble = false; } // 撤销悔棋 returnbtn.onclick = function (e) { if (!returnAble) { return; } // 我,撤销悔棋 chressBord[_nowi][_nowj] = 1; //我,已占位置 oneStep(_nowi, _nowj, me); for (var k = 0; k < count; k++) { if (wins[_nowi][_nowj][k]) { myWin[k]++; _compWin[k] = computerWin[k]; computerWin[k] = 6; //这个位置对方不可能赢 } if (myWin[k] == 5) { resultTxt.innerHTML = '恭喜,你赢了!'; over = true; } }// 计算机撤销相应的悔棋 chressBord[_compi][_compj] = 2; //计算机,已占位置 oneStep(_compi, _compj, false); for (var k = 0; k < count; k++) { // 将可能赢的情况都减1 if (wins[_compi][_compj][k]) { computerWin[k]++; _myWin[k] = myWin[k]; myWin[k] = 6; //这个位置对方不可能赢 } if (computerWin[k] == 5) { resultTxt.innerHTML = '很遗憾,计算机赢了,继续加油哦!'; over = true; } } returnbtn.className += ' ' + 'unable'; returnAble = false; backAble = true; } // 计算机下棋 var computerAI = function () { var myScore = []; var computerScore = []; var max = 0; var u = 0, v = 0; for (var i = 0; i < 15; i++) { myScore[i] = []; computerScore[i] = []; for (var j = 0; j < 15; j++) { myScore[i][j] = 0; computerScore[i][j] = 0; } } for (var i = 0; i < 15; i++) { for (var j = 0; j < 15; j++) { if (chressBord[i][j] == 0) { for (var k = 0; k < count; k++) { if (wins[i][j][k]) { if (myWin[k] == 1) { myScore[i][j] += 200; } else if (myWin[k] == 2) { myScore[i][j] += 400; } else if (myWin[k] == 3) { myScore[i][j] += 2000; } else if (myWin[k] == 4) { myScore[i][j] += 10000; } if (computerWin[k] == 1) { computerScore[i][j] += 220; } else if (computerWin[k] == 2) { computerScore[i][j] += 420; } else if (computerWin[k] == 3) { computerScore[i][j] += 2100; } else if (computerWin[k] == 4) { computerScore[i][j] += 20000; } } } if (myScore[i][j] > max) { max = myScore[i][j]; u = i; v = j; } else if (myScore[i][j] == max) { if (computerScore[i][j] > computerScore[u][v]) { u = i; v = j; } } if (computerScore[i][j] > max) { max = computerScore[i][j]; u = i; v = j; } else if (computerScore[i][j] == max) { if (myScore[i][j] > myScore[u][v]) { u = i; v = j; } } } } } _compi = u; _compj = v; oneStep(u, v, false); chressBord[u][v] = 2;//计算机占据位置 for (var k = 0; k < count; k++) { if (wins[u][v][k]) { computerWin[k]++; _myWin[k] = myWin[k]; myWin[k] = 6; //这个位置对方不可能赢了 if (computerWin[k] == 5) { resultTxt.innerHTML = '很遗憾,计算机赢了,继续加油哦!'; over = true; } } } if (!over) { me = !me; } backAble = true; returnAble = false; var hasClass = new RegExp('unable').test(' ' + returnbtn.className + ' '); if (!hasClass) { returnbtn.className += ' ' + 'unable'; } } //绘画棋盘 var drawChessBoard = function () { for (var i = 0; i < 15; i++) { context.moveTo(15 + i * 30, 15); context.lineTo(15 + i * 30, 435); context.stroke(); context.moveTo(15, 15 + i * 30); context.lineTo(435, 15 + i * 30); context.stroke(); } } //画棋子 var oneStep = function (i, j, me) { context.beginPath(); context.arc(15 + i * 30, 15 + j * 30, 13, 0, 2 * Math.PI); // 画圆 context.closePath(); //渐变 var gradient = context.createRadialGradient(15 + i * 30 + 2, 15 + j * 30 - 2, 13, 15 + i * 30 + 2, 15 + j * 30 - 2, 0); if (me) { gradient.addColorStop(0, '#0a0a0a'); gradient.addColorStop(1, '#636766'); } else { gradient.addColorStop(0, '#d1d1d1'); gradient.addColorStop(1, '#f9f9f9'); } context.fillStyle = gradient; context.fill(); } //销毁棋子 var minusStep = function (i, j) { //擦除该圆 context.clearRect((i) * 30, (j) * 30, 30, 30); // 重画该圆周围的格子 context.beginPath(); context.moveTo(15 + i * 30, j * 30); context.lineTo(15 + i * 30, j * 30 + 30); context.moveTo(i * 30, j * 30 + 15); context.lineTo((i + 1) * 30, j * 30 + 15); context.stroke(); } Copyright © 2018-2019 TRHX'S BLOG |    鄂ICP备19003281号-3 |  中国互联网违法和不良信息举报中心 推荐使用1920*1080分辨率、谷歌或者火狐浏览器浏览此页 |  站点地图 |   |  RSS订阅 |  996.ICU |  申请友链 |  Powered By Hexo |  Hosted By GitHub Pages 部分资料来源于网络,版权属于其合法持有人,只供学习交流之用,非商务用途。如有侵犯您的权益,请及时告知删除。互动交流时请遵守理性,宽容,换位思考的原则。 document.onkeydown=function (e){ var currKey=0,evt=e||window.event; currKey=evt.keyCode||evt.which||evt.charCode; if (currKey == 123) { window.event.cancelBubble = true; window.event.returnValue = false; } }"},{"title":"","date":"2020-02-27T12:02:54.700Z","updated":"2020-02-27T12:02:54.700Z","comments":true,"path":"box/index.html","permalink":"https://www.itrhx.com/box/index.html","excerpt":"","text":"百宝箱丨TRHX'S BLOG 开发社区 代码托管 语言文档 技能训练 在线平台 高校平台 游戏编程 HOT Pythoner 文档资料 博客收藏 学习资源 组织社区 爬虫相关 HOT 学习教程 在线视频 博客论坛 学习平台 常用工具 站长工具 HOT IT工具箱 文件处理 HOT 设计素材 效率软件 HOT 服务平台 云服务商 众包平台 站内游戏 HOT 更多导航 关于本页 隐藏/显示侧边栏 博客首页 友情链接 评论留言 关于博主 (function(a,h,g,f,e,d,c,b){b=function(){d=h.createElement(g);c=h.getElementsByTagName(g)[0];d.src=e;d.charset=\"utf-8\";d.async=1;c.parentNode.insertBefore(d,c)};a[\"SeniverseWeatherWidgetObject\"]=f;a[f]||(a[f]=function(){(a[f].q=a[f].q||[]).push(arguments)});a[f].l=+new Date();if(a.attachEvent){a.attachEvent(\"onload\",b)}else{a.addEventListener(\"load\",b,false)}}(window,document,\"script\",\"SeniverseWeatherWidget\",\"//cdn.sencdn.com/widget2/static/js/bundle.js?t=\"+parseInt((new Date().getTime() / 100000000).toString(),10))); window.SeniverseWeatherWidget('show', { flavor: \"slim\", location: \"WX4FBXXFKE4F\", geolocation: true, language: \"auto\", unit: \"c\", theme: \"auto\", token: \"a39cd5a0-4024-4cb2-85c6-0250317058db\", hover: \"enabled\", container: \"tp-weather-widget\" }) 开发社区 Stack Overflow 全球最受程序员欢迎的开发社区 CSDN 全球最大中文IT社区,为IT专业技术人员提供最全面的信息传播和服务平台 博客园 代码改变世界 V2EX V2EX = way to explore 掘金 一个帮助开发者成长的社区 SegmentFault 改变并提升人们获取知识的方式和效率,帮助更多的开发者获得成长与成功 开源中国 国内最大的开源技术社区 ITeye ITeye软件开发交流社区 - Java编程 Spring框架 Ajax技术 agile敏捷软件开发 ruby on rails实践 51CTO 技术成就梦想 ITPUB 全球最大的学习分享平台 知乎 国内最受欢迎的知识性问答社区 简书 创作你的创作 云+社区 来自腾讯的开发者技术分享社区 云栖社区 阿里云面向开发者的开放型技术平台 代码托管 Github 全球最大的面向开源及私有软件项目的托管平台 Gitlab 支持无限的公有项目和私有项目的代码托管平台 Bitbucket 同时支持 Git 和 Mercurial 这两个版本控制软件,免费的私有仓库,支持5人以内的合作开发 SourceForge 又称 SF.net,是开源软件开发者进行开发管理的集中式场所 Coding 国内首个一站式云端软件服务平台 Gitee 国内最大的开源社区 OSChina 的代码托管平台 阿里云代码托管 阿里云旗下代码托管平台 百度效率云 百度云旗下的 Git 代码托管平台 语言文档 Zeal 脱机文档浏览器,包含196种语言API文档,支持Windows、Linux和macOS Dash 适用于Mac OS平台的软件编程文档管理工具,可以浏览API文档,以及管理代码片段工具。自带了丰富的API文档,涉及各种主流的编程语言和框架 DevDocs 在快速,有条理和可搜索的界面中结合了多个API文档,可以在移动设备上离线运行,并且可以安装在Chrome上 C/C++ C/C++ API 文档 C# C# API 文档 Java Java API 文档 .NET .NET API 文档 PHP PHP API 文档 JavaScript JavaScript API 文档 Python Python API 文档 Android Android API 文档 iOS iOS API 文档 SQL SQL API 文档 Swift Swift API 文档 Ruby Ruby API 文档 GO GO API 文档 R R API 文档 MATLAB MATLAB API 文档 Node.js Node.js API 文档 HTML HTML API 文档 CSS CSS API 文档 Redis Redis API 文档 MongoDB MongoDB API 文档 Django Django API 文档 在线平台 LeetCode 全球极客挚爱的技术成长平台 Topcoder 全世界规模最大的程序竞赛网站,也会有一些算法竞赛,适合一些高端的或者搞ACM的,也会举办一些比赛 Codeforces 俄罗斯最大的算法比赛网站 Hihocoder 技术团队来自原北京大学POJ (PKU Online Judge)开发团队,收集了全球范围内很多地区、高校举办的比赛试题, 提供365天*24小时的在线提交、评判程序的服务 LintCode 被称作中文版的leetcode,也是可以做为编程能力提升的一个中转站 SPOJ 波兰的算法刷题网站 NEUQ OJ 一个在线的判题平台 洛谷 创办于2013年,致力于为参加noip、noi、acm的选手提供清爽、快捷的编程体验 牛客网 中国最大的IT题库 C语言网 在这里可以参加包括ACM、NOI在内的各种C/C++/java程序比赛,也可以DIY举办各类程序比赛活动! 计蒜客 计蒜客OI题库致力于为参加noi、noip、信息学竞赛的选手提供优秀的Online Judge系统 高校平台 POJ 北京大学程序在线评测系统 FDU OJ 复旦大学程序在线评测系统 TJ OJ 同济大学程序在线评测系统 USTC OJ 中国科学技术大学程序在线评测系统 ZOJ 浙江大学程序在线评测系统 HDU OJ 杭州电子科技大学程序在线评测系统 CSU-ACM 中南大学程序在线评测系统 HOJX 哈尔滨工业大学程序在线评测系统 HRBUST OJ 哈尔滨理工大学程序在线评测系统 PowerOJ 西南科技大学程序在线评测系统 SCU OJ 四川大学程序在线评测系统 FZU CoidngOJ 福州大学程序在线评测系统 NBUT OJ 宁波工程学院程序在线评测系统 Lutece 电子科技大学程序在线评测系统 武汉大学 ACM 协会 武汉大学 ACM 协会 ZJUT OJ 浙江工业大学程序在线评测系统 游戏编程 CheckiO 面向初学者和高级程序员的编码游戏,使用Python和JavaScript解决棘手的挑战和有趣的任务,从而提高您的编码技能 Coding Games 支持包括PHP、C、JavaScript在内的20多种编程语言。用户界面功能强大,可以定制 Codewars 一个外国的在线练习编程的网站,做题的过程类似打怪,做题升级,而且可以看到别人的解法,里面有很多巧妙的写法可以学习 CodeCombat 一个面向学生的游戏和CS学习平台。这是一个社区项目,有数百玩家自愿提供支持。支持语言包括Java、JS、Python、Lua、CoffeeScript Screeps 在游戏中学习JavaScript。世界上第一款针对程序员的MMO沙盒游戏 VIM Adventures 玩游戏的时候学VIM Cyber-Dojo 一个提供给程序员们练习写程序的地方。支持语言包括JavaScript、Java、Python、PHP、Ruby和很多其他语言 Elevator Saga 电梯编程游戏,跟随关卡解决所有挑站,使用语言为JavaScript Ruby Quiz 一个Ruby程序员提供的每周编程挑战项目 hacker.org 这项挑战由一系列本设计来强化你黑客技巧的解密、诡计、测试、烧脑环节组成。想要通关本系列,你必须学会解密、编码、渗透 Ruby Warrior 玩游戏学Ruby,通过Ruby脚本来控制一个Warrior通过每一关,每一关的代码难度都会有所增加,使玩家逐渐了解Ruby基本的函数、控制、变量、数组等语言特性的用法 文档资料 Python 官方文档 Python 官方文档 Python 标准库 Python 标准库 Python Requests Python Requests 文档 Python Urllib Python Urllib 文档 Python Selenium Python Selenium 中文翻译文档 正则表达式 Python 正则表达式官方文档 Beautiful Soup Beautiful Soup 文档 Scrapy Scrapy 爬虫框架官方文档 PySpider PySpider 爬虫框架官方文档 Matplotlib Matplotlib 2D绘图库 官方中文文档 Numpy Numpy 科学计算 官方中文文档 Pandas Pandas 结构化数据分析 官方中文文档 博客收藏 廖雪峰 廖雪峰的官方网站 - 研究互联网产品和技术,提供原创中文精品教程 崔庆才 崔庆才的个人博客,专注PHP,Python,爬虫,深度学习,机器学习,数据分析 莫烦Python 专注Python、机器学习、深度学习 唐松 专注Python网络爬虫, 数据科学, 数据挖掘, 数据分析 捕蛇者说 编程、程序员、Python FxxkPython 学习python的正确姿势 wistbean Python 大佬 Piglei Python 大佬 TendCode Python 大佬 追梦人物的博客 Python Django 大佬 the5fire 《Django企业开发实战》作者,关注Python、Django、Vim、Linux、Web开发 小明明S À DOMICILE 《Python Web开发实战》作者,Python 大佬 Python之禅 Python 大佬 Python 知识圈 Python知识圈 - 实用的Python教程网站 Python 教程网 小詹学Python,专注Python学习 烂笔头 j_hao104 Python大佬 咸鱼日常 专注Python爬虫,有许多JS逆向文章 AnSheng Python 全栈大佬 夏溪辰 云栖社区特邀爬虫工程师,Python大佬 高级农民工 Python大佬 云爬虫技术研究笔记 Lateautumn4lin 爬虫开发工程师,多年反爬虫破解经验,沉迷数据分析和黑客增长,CSDN博客专家,华为云享专家 云爬虫技术研究笔记(CSDN) Lateautumn4lin 爬虫开发工程师,多年反爬虫破解经验,沉迷数据分析和黑客增长,CSDN博客专家,华为云享专家 Jack Cui CSDN博客专家,Python 大佬 学习资源 Python爬虫人工智能学习教程 Python爬虫人工智能学习教程分享 Python 中文学习大本营 Python 中文学习大本营 Python 资源大全中文版 Python 资源大全中文版 爱湃森 各种 Python 教程 组织社区 PyChina Python 中国社区 PyCon China 中国 Python 开发者大会 蠎周刊 蠎周刊 - 汇集全球蠎事儿 爬虫相关 镀金的天空 GlidedSky 镀金的天空,在线爬虫练习题库 夜幕爬虫安全论坛 一个专注于爬虫与 PC/Web/ 移动端安全领域技术交流的社区,社区由夜幕团队 NightTeam 创办,旨在提升开发者对爬虫与软件安全防护的理解 西刺免费代理IP 每日更新免费HTTP代理,所有代理均为6675端口高匿代理,可隐藏IP 爬虫IP代理池 爬虫IP代理池 云打码 采用全球领先的秒传识别系统,50%图片零秒识别,人工平均处理时间0-3秒 超级鹰 专业的验证码云端识别服务,让验证码识别更快速、更准确、更强大 八爪鱼采集器 一款使用简单、功能强大的网络爬虫工具,完全可视化操作,无需编写代码,内置海量模板,支持任意网络数据抓取 Python 逆向 Python 逆向相关资源 Python 爬虫集合 Python 爬虫集合 Python 入门网络爬虫之精华版 Python 入门网络爬虫之精华版 爬虫项目进阶实战 Python3 爬虫项目进阶实战、JS加解密、逆向教程、css 加密、字体加密 Python 模拟登陆一些大型网站 Python 模拟登陆一些大型网站 系统化学习 Python 爬虫 系统化学习 Python 爬虫 Python3 网络爬虫实战 Python3 网络爬虫实战 在线视频 腾讯课堂 腾讯推出的专业在线教育平台,聚合大量优质教育机构和名师 网易云课堂 网易旗下一个专注职业技能提升的在线学习平台。立足于实用性的要求,与多家教育培训机构和行业的专家、讲师建立合作 中国大学 MOOC 中国大学MOOC(慕课),国家精品课程在线学习平台 黑马程序员 致力于培养中级程序员,是业内以口碑闻名的IT教育培训机构 课工场 更可靠的IT就业教育平台,针对大学生量身定制人工智能、大数据、云计算、区块链、Java大数据开发等大学生IT培训课程 极客学院 极客学院作为中国专业IT职业在线教育平台,拥有海量高清IT职业课程,涵盖30+个技术领域 慕课网 慕课网(IMOOC)是IT技能学习平台。慕课网(IMOOC)提供了丰富的移动端开发、php开发、web前端、android开发以及html5等视频教程资源公开课 尚硅谷 尚硅谷Java培训,谷粉与老学员为你推荐的Java培训、Web前端培训、前端培训、大数据培训、Python培训;0基础入学,学员就业起薪屡创新高! 实验楼 国内领先的IT在线编程及在线实训学习平台,专业导师提供精选的实践项目,创新的技术使得学习者无需配置繁琐的本地环境,随时在线流畅使用 优达学城 Udacity是来自硅谷的前沿技术平台,为广大学子提供WEB前端开发、Python/JAVA编程、IOS/Android开发、人工智能开发等一系列在线课程及实战项目,满足学员灵活的学习需求 51CTO学院 51CTO学院IT职业在线教育平台是依托12年行业品牌、1400万IT技术用户建立的专业IT技能学习培训平台,已签约1000多位技术专家发布了12万个自学式实战视频教程 CSDN 学院 CSDN 学院作为IT在线教育平台,涵盖人工智能、考试认证、移动开发、大数据技术领域职业课程 老男孩IT教育 隶属北京一天天教育科技有限公司,是一直专注于Linux培训、Linux系统及架构师培训、Python培训、网络安全培训,大数据实战的高端培训机构 千锋教育 千锋教育 - 坚持教育初心,坚持面授品质,IT培训良心品牌 博客论坛 鱼C工作室 鱼C工作室-免费编程视频教学|Python教学|Web开发教学|全栈开发教学|C语言教学|汇编教学|Win32开发|加密与解密|Linux教学 吾爱破解 致力于软件安全与病毒分析的前沿,丰富的技术版块交相辉映,由无数热衷于软件加密解密及反病毒爱好者共同维护 廖雪峰 廖雪峰的官方网站 - 研究互联网产品和技术,提供原创中文精品教程 崔庆才 崔庆才的个人博客,专注PHP,Python,爬虫,深度学习,机器学习,数据分析 莫烦Python 专注Python、机器学习、深度学习 唐松 专注Python网络爬虫, 数据科学, 数据挖掘, 数据分析 阮一峰 上海财经大学世界经济博士研究生,计算机科普博主,对自由软件有着坚定不移的信念 学习平台 菜鸟教程 提供了编程的基础技术教程, 介绍了HTML、CSS、Javascript、Python,Java,Ruby,C,PHP , MySQL等各种编程语言的基础知识 W3school 领先的 Web 技术教程 C语言网 C语言网 - 领先实用的编程在线学习网站 前端网 前端网,最好的自学web前端网站 牛客网 牛客网 - 互联网求职神器和备考学习平台 How2J How2J的Java教程, 内容涵盖J2SE、WEB前端、J2EE、框架技术等全面的Java内容 站长工具 新浪短网址 多种后缀短网址生成 百度短网址 百度旗下专业的网址缩短服务 站长工具 - 站长之家 站长工具,SEO工具,权重查询,收录查询,PR查询,ICP备案查询,whois查询,友情链接查询,反向链接查询,网站测试,IP查询,Alexa查询 阿里云 whois 查询 whois查询,域名whois,域名注册信息,whois查询工具,whois信息,域名信息 NnameBeta 国际域名搜索、域名注册、国别域名注册、域名比价 Domcomp 域名比价,Domain Name Price and Availability. 仿站工具箱 在线仿站工具箱 超级 SEO 外链工具 网站自动化宣传机器/免费的超级外链工具可批量增加外链 百度站长平台 百度搜索资源平台 - 让网站更具价值 搜狗站长平台 搜狗站长平台 - 全面掌握在搜狗搜索中的数据表现 360 站长平台 360 站长平台 - 给网站带来更多流量和展现 Google 站长平台 Google 网站站长 - 支持、学习、互动交流和 Search Console – Google Bing 网站管理员工具 Bing 网站管理员工具 百度广告联盟 百度广告联盟为您的流量增值 Google AdSense Google 广告平台 百度统计 百度统计 — 最大的中文网站分析平台 友盟+ 国内领先的第三方全域数据智能服务商 ICP/IP地址/域名信息备案管理系统 工业和信息化部ICP/IP地址/域名信息备案管理系统 全国互联网安全管理服务平台 公安备案网 - 全国互联网安全管理服务平台 IT工具箱 在线工具 - 程序员的工具箱 站长工具、代码格式化、压缩、加密、解密、下载链接转换等 在线工具 - OSCHINA.NET社区 常用文档、常用对照表、代码处理、Html/Js/Css工具、加密/转码工具等 记磊工具箱 Dns检测、CSS格式化、超级Ping、端口扫描等 孟坤工具箱 css一键美化、文本差异比较、代码高亮等 Syntax Highlight Syntax Highlight Code In Word Documents,在Word文档中插入漂亮的代码 Text to ASCII Art Generator Text to ASCII Art Generator,字符串转成 ASCII 码图案 MDEditor 开源在线 Markdown 编辑器 临时邮箱 匿名注册不常用的网站/论坛,保护隐私免骚扰 SM.MS SM 免费图床,每个文件最大支持 5MB 路过图床 免费公共图床,支持最大10MB、批量上传 Greasy Fork 安全、实用的用户脚本大全 Hello World 大全 收集了大约481种 Hello World 程序,涵盖了目前已知的所有编程语言,另加上 67 人类语言 动画展示各种路径搜索算法 动画展示各种路径搜索算法 IT eBooks 可以下载IT电子书籍的网站(英文) GEEKTyper 在线模拟黑客工作的虚拟桌面系统,提供多种黑客工作的场景 免费计算机编程类中文书籍 免费计算机编程类中文书籍 EaseUS Partition Master 磁盘分区管理软件,不用重装系统,就可以重新划分磁盘空间 文件处理 Convertio 在线文件转换工具,支持超过309种不同的文档、图像、电子表格、电子书、文档、演示文稿、音频和视频格式 Office-Converter 免费在线转换视频,在线音频转换,在线图形转换,在线文档转换和在线压缩格式 TinyPNG PNG/JPG图片在线压缩利器 Squoosh Google开源在线压缩、调整工具,支持WebP ILoveIMG 永远免费的在线图片处理工具,可在线编辑,压缩、裁剪、转换、水印等 Smallpdf Smallpdf - A Free Solution to all your PDF Problems,PDF压缩、转换、分割、合并等 PHOTOMOSH 故障艺术在线生成,可以输出jpg、gif和视频 稿定抠图 免费在线抠图软件,图片快速换背景-抠白底图 U钙网 完全免费的LOGO在线设计制作工具 SVGOMG SVG在线压缩平台 在线图片透明圆角处理 在线图片透明圆角处理 草料二维码 国内创建二维码在线应用 Logaster 在线免费创建简单logo及名片设计 Preloaders Loading 懒加载动画在线制作 Loading 制作GIF、SVG、CSS加载动画图标 waifu2x 图片智能无损放大2倍,适合动漫、插画等 智图 腾讯ISUX前端团队开发的一个专门用于图片压缩和图片格式转换的平台 音乐免费下载 全网音乐免费下载工具 OK资源采集 OK资源采集-最新影视资源大全 网易见外工作台 针对视频、图片、文档、音频都可以进行翻译转写操作,每天两小时免费使用 HiPDF 一站式解决所有PDF相关的问题 视频鱼 在线下载各大网站视频的网站 ScreenToGif 开源、轻量级却非常强大的录屏软件,快速将屏幕录制成高清GIF 设计素材 Iconfont 阿里巴巴矢量图标库,提供矢量图标下载、在线存储、格式转换等功能 Font Awesome 一个基于CSS 和 LESS 的字体和图标工具包 Flaticon 海量扁平化免费的图标库 icons8 独特系统平台风格和web图标库,下载免费图标,音乐 千图网 海量原创设计模板免费下载 昵图网 国内海量平面免费素材下载 千库网 免费 png 图片背景素材下载 Pexels 才华横溢的摄影作者在这里免费分享最精彩的素材照片和视频 必应壁纸 必应每日高清壁纸 Piqsels 精美的免版税图库 私藏字体 优质字体免费下载站 第一 PPT 网 免费 PPT 模板下载 吾道幻灯片 全新的office生产力工具,支持演示文稿、PPT模板、协同办公,可以帮助用户轻松创建具有视觉吸引力的幻灯片 Mixkit 免费、高质量、可商用的视频素材分享网站 The Stocks 对各大图片网站进行整合,免费优质图片下载 极简壁纸 高质量精品壁纸网站 NASA Image and Video Library 美国国家航天局的官方库,从此太空类的素材再也不是问题 Unsplash 质量超高的免费图片素材库,无需注册,直接下载 WordArt 文字云工具 效率软件 分流抢票 全程自动抢票,自动抢候补,自动识别验证码,多线程秒单、稳定捡漏,支持多天、多车次、多席别、多乘客等功能 PanDownload 百度网盘下载神器 Quicker 为常用操作建立捷径,PC 快捷动作面板,让效率触手可及! 万彩办公大师 免费、轻松处理文档/音视频/图片的工具 LICEcap 简洁易用的动画屏幕录制软件,它可将屏幕录像的内容直接保存为高质量(每帧颜色数量可超过256)GIF动态图片格式 Snipaste 简单但强大的截图工具,支持截图 + 贴图 FSCapture 一个强大的,轻量级的,功能齐全的屏幕捕获工具 Everything 速度最快的的文件搜索工具 DeskPins 顶置任意窗口 TrafficMonitor 一个用于显示当前网速、CPU及内存利用率的桌面悬浮窗软件 PicGo 由 electronic-vue 构建的简单而精美的图片上传工具 PowerToys 微软为 Windows 系统推出的一系列免费实用小工具合集 Dism++ 一款根据微软底层的架构结构设计的一个系统维护工具,全球第一款基于 CBS 的 Dism GUI 实现 ColorPix 屏幕取色小工具 CCleaner 一款免费的系统优化和隐私保护工具 GifCam 集录制与剪辑为一体的屏幕 GIF 动画制作工具,录制后的动画可以逐帧编辑 EV录屏 一款免费并且不添加水印的录屏工具 Fliqlo 一款极简主义的时钟屏保软件 Fences 栅栏管理桌面,使桌面更加整洁有条理 Q-dir 多窗口文件整理工具 WGestures 鼠标手势工具 XMind 一个全功能的思维导图和头脑风暴软件 速盘 免登录,自动查询提取码,极速的度盘下载工具 f.lux 国外开源的护眼软件,通过根据时间调节屏幕颜色,减少蓝光对视力的影响 云服务商 阿里云 阿里云 - 为了无法计算的价值 腾讯云 腾讯云 - 产业智变 云启未来 百度云 百度云 - 计算无限可能 华为云 华为云 - +智能,见未来 京东云 京东云 - 遇见无限可能 西部数码 西部数码 - 云服务器、虚拟主机、域名注册17年知名云计算服务提供商! 景安云 景安云 - 专业的数据中心服务商 七牛云 七牛云 - 国内领先的企业级云服务商 又拍云 又拍云 - 加速在线业务-CDN-云存储 美橙互联 美橙互联 - 域名注册、企业建站、云服务器、企业网络推广整体解决方案服务商! UCloud UCloud - 中立 安全 可信赖的云计算服务商 AWS AWS 云服务 - 专业的大数据和云计算服务以及云解决方案提供商 Microsoft Azure Azure. Invent with purpose. GoDaddy GoDaddy - 提供域名注册和互联网主机服务的美国公司 Cloudflare Cloudflare - 网络性能和安全公司 jsDelivr jsDelivr - A free, fast, and reliable Open Source CDN for npm and GitHub 众包平台 猿急送 专注于 IT 众包领域,职位内容大多集中于 UI 设计、产品设计、程序开发、产品运营等需求 开源众包 开源中国旗下外包网站,项目大多是团队的整包项目,适合多人组团接单 外包大师 PMCAFF旗下的一个众包开发平台,目前以技术开发为主,以众包开发和自有开发相结合形式运营 人人开发 集可视化开发,应用市场,威客众包,PaaS云于一体的企业级应用服务平台 快码 提供智能硬件、各种智能共享项目解决方案,为互联网创业者提供APP、小程序、公众号开发。 我爱方案网 专注于硬件类外包,电子方案开发供应链众包平台,软件外包,方案,硬件开发方案,硬件设计开发 英选 提供可信赖的定制开发外包服务,包括企业品牌官网、电商系统及创新定制产品开发 智筹 为企业&创业者提供互联网高级人才直租服务。按次直租,解决临时、突发问题;按月直租,建立长期兼职合作;按任务直租,解决有明确预算的外包任务 开发邦 互联网软件定制开发与软件外包开发服务,十年互联网软件定制开发经验 码市 Coding 推出的互联网软件外包服务平台,意在连接需求方与广大开发者。让项目的需求方快速的找到合适的开发者,完成项目开发工作 自由职客 自由职客是权威的IT互联网行业灵活用工交易平台,外包,众包,兼职,招聘,erp,sap 解放号 解放号众包平台提供软件开发外包、人力驻场服务等软件项目外包服务。解放号的软件项目交付全流程可视化监控与全生命周期管理能力 程序员客栈 领先的程序员自由工作平台,38万+优秀开发者,您的专属云端开发团队,BAT级别的开发者,标准化的服务和交付 码易 智网易联旗下IT软件服务平台,集软件商城、企业应用、电商软件、crm软件、商务服务平台于一体的一站式软件外包开发服务平台 电鸭社区 电鸭社区旨在推动自由工作方式在国内渐进式发展,区别于传统方式的工作职位,倡导「只工作,不上班」的工作心态 Sxsoft 中国最早的外包服务平台,18年口碑服务,20万程序员、100+专业软件开发公司,专注解决各类软件开发需求 实现网 为企业提供BAT等名企背景的、靠谱的开发设计兼职人才和自由职业者,满足企业项目外包、驻场开发、远程兼职、技术咨询等短期人力需求 智城外包网 零佣金开发资源平台,认证担保,全程无忧,专业的软件外包网和项目外包、项目开发、人力外派、短期招聘、人力资源交易平台 站内游戏 2048 网页版 2048 小游戏 圈小猫 点击圆点围住小猫 3D 元素周期表 3D 网页展示元素周期表 五子棋 网页版简易五子棋 吃豆人 躲避怪物,吃掉豆子 网页钢琴 简易网页版钢琴 更多导航 创造狮导航 创造狮,一个创意工作者的导航,专注分享正版优质设计、前端、产品、运营的书签导航,设计教程、设计规范、颜色搭配、灵感创意、前端框架、开发者工具、互联网新品推荐、运营数据分析、自媒体和工具利器好用的分类导航大全 大数据导航 大数据导航,以大数据产业为主,大数据工具为辅,给用户提供一个更加快速找到大数据相关的工具平台 优设导航 优设网站导航为设计师提供ps教程、UI设计、素材下载、高清图库、配色方案、用户体验、网页设计等全方位设计师网站导航指引 牛导航 实用工具导航 聚BT 聚BT - 聚合最优质的BT、磁力资源 ShareHub ShareHub - 资源和工具的集合 狼牌工作网址导航 工具,资源,方法,All IN ONE的办公工作网址导航 COPYRIGHT 2018 - 2020 WEBSTACK 丨 DESIGNED BY VIGGO 丨 CHANGED BY TRHX $(document).ready(function() { $(document).on('click', '.has-sub', function(){ var _this = $(this) if(!$(this).hasClass('expanded')) { setTimeout(function(){ _this.find('ul').attr(\"style\",\"\") }, 300); } else { $('.has-sub ul').each(function(id,ele){ var _that = $(this) if(_this.find('ul')[0] != ele) { setTimeout(function(){ _that.attr(\"style\",\"\") }, 300); } }) } }) $('.user-info-menu .hidden-sm').click(function(){ if($('.sidebar-menu').hasClass('collapsed')) { $('.has-sub.expanded > ul').attr(\"style\",\"\") } else { $('.has-sub.expanded > ul').show() } }) $(\"#main-menu li ul li\").click(function() { $(this).siblings('li').removeClass('active'); // 删除其他兄弟元素的样式 $(this).addClass('active'); // 添加当前元素的样式 }); $(\"a.smooth\").click(function(ev) { ev.preventDefault(); public_vars.$mainMenu.add(public_vars.$sidebarProfile).toggleClass('mobile-is-visible'); ps_destroy(); $(\"html, body\").animate({ scrollTop: $($(this).attr(\"href\")).offset().top - 30 }, { duration: 500, easing: \"swing\" }); }); return false; }); var href = \"\"; var pos = 0; $(\"a.smooth\").click(function(e) { $(\"#main-menu li\").each(function() { $(this).removeClass(\"active\"); }); $(this).parent(\"li\").addClass(\"active\"); e.preventDefault(); href = $(this).attr(\"href\"); pos = $(href).position().top - 30; });"}],"posts":[{"title":"用 VPS 搭建一个自己的 SSR 服务器","slug":"A61-build-a-SSR-server-with-VPS","date":"2020-01-10T13:38:13.786Z","updated":"2020-03-02T08:02:23.404Z","comments":true,"path":"2020/01/10/A61-build-a-SSR-server-with-VPS/","link":"","permalink":"https://www.itrhx.com/2020/01/10/A61-build-a-SSR-server-with-VPS/","excerpt":"俗话说得好:预先善其事,必先利其器,作为一个程序员,经常会用到 GitHub、Google、Stack Overflow 啥的,由于国内政策原因,想要访问国外网站就得科学上网,最常见的工具就是 ShadowsocksR,又被称为酸酸乳、SSR、小飞机,目前市面上有很多很多的机场,价格也不是很高,完全可以订阅别人的,但是订阅别人的,数据安全没有保障,有可能你的浏览历史啥的别人都能掌握,别人也有随时跑路的可能,总之,只有完全属于自己的东西才是最香的!","text":"俗话说得好:预先善其事,必先利其器,作为一个程序员,经常会用到 GitHub、Google、Stack Overflow 啥的,由于国内政策原因,想要访问国外网站就得科学上网,最常见的工具就是 ShadowsocksR,又被称为酸酸乳、SSR、小飞机,目前市面上有很多很多的机场,价格也不是很高,完全可以订阅别人的,但是订阅别人的,数据安全没有保障,有可能你的浏览历史啥的别人都能掌握,别人也有随时跑路的可能,总之,只有完全属于自己的东西才是最香的! 购买 VPSVPS(Virtual Private Server)即虚拟专用服务器技术,在购买 VPS 服务器的时候要选择国外的,推荐 Vultr,国际知名,性价比比较高,最低有$2.5/月、$3.5/月的,个人用的话应该足够了。 点击链接注册 Vultr 账号:https://www.vultr.com/?ref=8367048,目前新注册用户充值10刀可以赠送50刀,注册完毕之后来到充值页面,最低充值10刀,可以选择支付宝或者微信支付。 充值完毕之后,点击左侧 Products,选择服务器,一共有16个地区的,选择不同地区的服务器,最后的网速也有差别,那如何选择一个速度最优的呢?很简单,你可以一次性选择多个服务器,都部署上去,搭建完毕之后,测试其速度,选择最快的,最后再把其他的都删了,可能你会想,部署多个,那费用岂不是很贵,这里注意,虽然写的是多少钱一个月,而实际上它是按照小时计费的,从你部署之后开始计费,$5/月 ≈ $0.00694/小时,你部署完毕再删掉,这段时间的费用很低,可以忽略不计,一般来说,日本和新加坡的比较快一点,也有人说日本和新加坡服务器的端口封得比较多,容易搭建失败,具体可以自己测试一下,还有就是,只有部分地区的服务器有$2.5/月、$3.5/月的套餐,其中$2.5/月的只支持 IPv6,可以根据自己情况选择,最后操作系统建议选择 CentOS 7 x64 的,不然有可能搭建失败,后面还有个 Enable IPv6 的选项,对 IPv6 有需求的话可以勾上,其他选项就可以不用管了。 部署成功后,点 Server Details 可以看到服务器的详细信息,其中有 IP、用户名、密码等信息,后面搭建 SSR 的时候会用到,此时你可以 ping 一下你的服务器 IP,如果 ping 不通的话,可以删掉再重新开一个服务器。 搭建 SSR我们购买的是虚拟的服务器,因此需要工具远程连接到 VPS,如果是 Mac/Linux 系统,可以直接在终端用 SSH 连接 VPS: 1ssh root@你VPS的IP -p 22 (22是你VPS的SSH端口) 如果是 Windows 系统,可以用第三方工具连接到 VPS,如:Xshell、Putty 等,可以百度下载,以下以 Xshell 为例: 点击文件,新建会话,名称可以随便填,协议为 SSH,主机为你服务器的 IP 地址,点击确定,左侧双击这个会话开始连接,最开始会出现一个 SSH安全警告,点击接受并保存即可,然后会让你输入服务器的用户名和密码,直接在 Vultr 那边复制过来即可,最后看到 [root@vultr ~]# 字样表示连接成功。 连接成功后执行以下命令开始安装 SSR: 1wget --no-check-certificate https://freed.ga/github/shadowsocksR.sh; bash shadowsocksR.sh 如果提示 wget :command not found,可先执行 yum -y install wget,再执行上述命令即可。 执行完毕后会让你设置 SSR 连接密码和端口,然后按任意键开始搭建。 搭建成功后会显示你服务器 IP,端口,连接密码,协议等信息,这些信息要记住,后面使用 ShadowsocksR 的时候要用到。 安装锐速由于我们购买的服务器位于国外,如果遇到上网高峰期,速度就会变慢,而锐速就是一款专业的连接加速器,可以充分利用服务器带宽,提升带宽吞吐量,其他还有类似的程序如 Google BBR 等,可以自行比较其加速效果,以下以操作系统为 CentOS 6&7 锐速的安装为例。 如果你服务器操作系统选择的是 CentOS 6 x64,则直接执行以下命令,一直回车即可: 1wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/hombo125/doubi/master/appex.sh && bash appex.sh install '2.6.32-642.el6.x86_64' 如果你服务器操作系统选择的是 CentOS 7 x64,则需要先执行以下命令更换内核: 1wget --no-check-certificate -O rskernel.sh https://raw.githubusercontent.com/hombo125/doubi/master/rskernel.sh && bash rskernel.sh 如下图所示表示内核更换完毕,此时已经断开与服务器的连接,我们需要重新连接到服务器,再执行后面的操作: 重新连接到服务器后,再执行以下命令: 1yum install net-tools -y && wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/0oVicero0/serverSpeeder_Install/master/appex.sh && bash appex.sh install 然后一直回车即可,系统会自动安装锐速。 出现以下信息表示安装成功: 使用 SSR常见的工具有 ShadowsocksR、SSTap(原本是个游戏加速器,现在已经停止维护,但 GitHub 上仍然可以找到)等。 Shadowsocks 官网:https://shadowsocks.org/ShadowsocksR 下载地址:https://github.com/Anankke/SSRR-WindowsSSTap GitHub 地址:https://github.com/FQrabbit/SSTap-Rule 不管什么工具,用法都是一样的,添加一个新的代理服务器,服务器 IP、端口、密码、加密方式等等这些信息保持一致就行了。然后就可以愉快地科学上网了! 多端口配置经过以上步骤我们就可以科学上网了,但是目前为止只有一个端口,只能一个人用,那么如何实现多个端口多人使用呢?事实上端口、密码等信息是储存在一个叫做 shadowsocks.json 文件里的,如果要添加端口或者更改密码,只需要修改此文件即可。 连接到自己的 VPS,输入以下命令,使用 vim 编辑文件:vi /etc/shadowsocks.json 原文件内容大概如下: 1234567891011121314151617181920{ \"server\": \"0.0.0.0\", \"server_port\": 8686, \"server_ipv6\": \"::\", \"local_address\": \"127.0.0.1\", \"local_port\": 1081, \"password\":\"SSR12345\", \"timeout\": 120, \"udp_timeout\": 60, \"method\": \"aes-256-cfb\", \"protocol\": \"auth_sha1_v4_compatible\", \"protocol_param\": \"\", \"obfs\": \"http_simple_compatible\", \"obfs_param\": \"\", \"dns_ipv6\": false, \"connect_verbose_info\": 1, \"redirect\": \"\", \"fast_open\": false, \"workers\": 1} 增加端口,我们将其修改为如下内容: 1234567891011121314151617181920212223242526{ \"server\": \"0.0.0.0\", \"server_ipv6\": \"::\", \"local_address\": \"127.0.0.1\", \"local_port\": 1081, \"port_password\": { \"8686\":\"SSR1\", \"8687\":\"SSR2\", \"8688\":\"SSR3\", \"8689\":\"SSR4\", \"8690\":\"SSR5\" }, \"timeout\": 120, \"udp_timeout\": 60, \"method\": \"aes-256-cfb\", \"protocol\": \"auth_sha1_v4_compatible\", \"protocol_param\": \"\", \"obfs\": \"http_simple_compatible\", \"obfs_param\": \"\", \"dns_ipv6\": false, \"connect_verbose_info\": 1, \"redirect\": \"\", \"fast_open\": false, \"workers\": 1} 也就是删除原来的 server_port 和 password 这两项,然后增加 port_password 这一项,前面是端口号,后面是密码,注意不要把格式改错了!!!修改完毕并保存!!! 接下来配置一下防火墙,同样的,输入以下命令,用 vim 编辑文件:vi /etc/firewalld/zones/public.xml 初始的防火墙只开放了最初配置 SSR 默认的那个端口,现在需要我们手动加上那几个新加的端口,注意:一个端口需要复制两行,一行是 tcp,一行是 udp。 原文件内容大概如下: 12345678<?xml version=\"1.0\" encoding=\"utf-8\"?><zone> <short>Public</short> <service name=\"dhcpv6-client\"/> <service name=\"ssh\"/> <port protocol=\"tcp\" port=\"8686\"/> <port protocol=\"udp\" port=\"8686\"/></zone> 修改后的内容如下: 12345678910111213141516<?xml version=\"1.0\" encoding=\"utf-8\"?><zone> <short>Public</short> <service name=\"dhcpv6-client\"/> <service name=\"ssh\"/> <port protocol=\"tcp\" port=\"8686\"/> <port protocol=\"udp\" port=\"8686\"/> <port protocol=\"tcp\" port=\"8687\"/> <port protocol=\"udp\" port=\"8687\"/> <port protocol=\"tcp\" port=\"8688\"/> <port protocol=\"udp\" port=\"8688\"/> <port protocol=\"tcp\" port=\"8689\"/> <port protocol=\"udp\" port=\"8689\"/> <port protocol=\"tcp\" port=\"8690\"/> <port protocol=\"udp\" port=\"8690\"/></zone> 修改完毕并保存,最后重启一下 shadowsocks,然后重新载入防火墙即可,两条命令如下: 1/etc/init.d/shadowsocks restart 1firewall-cmd --reload 完成之后,我们新加的这几个端口就可以使用了 另外还可以将配置转换成我们常见的链接形式,如:ss://xxxxx 或 ssr://xxxxx,其实这种链接就是把 IP,端口,密码等信息按照一定的格式拼接起来,然后经过 Base64 编码后实现的,有兴趣或者有需求的可以自行百度。 扩展命令SSR 常用命令:启动:/etc/init.d/shadowsocks start停止:/etc/init.d/shadowsocks stop重启:/etc/init.d/shadowsocks restart状态:/etc/init.d/shadowsocks status卸载:./shadowsocks-all.sh uninstall更改配置参数:vim /etc/shadowsocks-r/config.json","categories":[{"name":"VPS","slug":"VPS","permalink":"https://www.itrhx.com/categories/VPS/"}],"tags":[{"name":"VPS","slug":"VPS","permalink":"https://www.itrhx.com/tags/VPS/"},{"name":"SSR","slug":"SSR","permalink":"https://www.itrhx.com/tags/SSR/"}]},{"title":"2019年总结【跨越今天,更不平凡】","slug":"A60-2019-summary","date":"2019-12-31T15:14:59.983Z","updated":"2020-02-07T07:34:41.017Z","comments":true,"path":"2019/12/31/A60-2019-summary/","link":"","permalink":"https://www.itrhx.com/2019/12/31/A60-2019-summary/","excerpt":"","text":"还记得小时候写作文,畅想2020会怎样怎样,光阴似箭,2020真的来了,度过了艰难的考试周,抽了个晚上,回想了一下,决定写一写总结吧,似乎以前都没写过呢,那干脆连带2017、2018也写写吧,重点写一写2019的,以后争取每年都做一下总结。 【2017】2017年高三,上半年就不用说了,所有高三考生都一个样吧,下半年考进了武汉的某二本院校,软件工程专业,现在回想起来,当时把时间浪费得太多了,最开始加了一个部门,后来退了(事实上啥也学不到,浪费时间 ),然后除了完成学校的课程以外,其他啥也没搞,剩下的时间基本上全拿来骑车了,从高一开始就热爱单车运动,刚上大学肯定得放飞自我了,没课的时候就天天和学长到处跑,都快把武汉跑了个遍了,当时还定了个计划,大学四年骑车去一次西藏或者青海湖,其他的什么都没想,也没有对以后具体干哪方面做过规划,这一年收获最多的应该就是路上的风景了。 【2018】2018上半年,大一下学期,学习方面就过了个英语四级,然后依旧热衷于我的单车,暑假的时候疯狂了一把,7天干了700多公里,从学校骑回家了,那个时候正是热的时候,白天基本上在三十度,从武汉往西边走,后面全是爬山,上山爬不动,下山刹不住,路上也遇到了不少牛逼人物,有徒步西藏的,有环游中国的,直播平台有好几十万粉丝的……遇到的人都很善良,很硬汉,这次经历从某种程度上来说也是一次成长吧,一次很有意义的骑行。 下半年,也就是大二开始,才慢慢开始重视专业知识的学习,大二上学期搭建了个人博客,开始尝试写博客,其实就是把博客当做笔记吧,记性不好,学了的东西容易忘记,忘记了可以经常翻自己博客再复习复习,自己踩过的坑也记录记录,后来没想到有些文章访问量还挺高的,在博客搭建方面也帮到了一些网友,最重要的是结识了不少博友,有各行各业的大佬,下半年也定了方向,开始专注Python的学习,从此开始慢慢熬夜,也渐渐地不怎么出去骑车了。 【2019】2019 总的来说,还比较满意吧,主要是感觉过得很充实,大三基本上每天一整天都是上机课,没有太多时间搞自己的,自己倾向于Python、网络爬虫、数据分析方面,然而这些课程学校都没有,每天晚上以及周六周日都是自己在学,找了不少视频在看,有时候感觉自己还是差点火候,感觉一个简单的东西人家看一遍就会,但是我要看好几遍,不管怎样,我还是相信勤能补拙的。 【学习方面】 [√] 通过软考中级软件设计师 [√] 成为入党积极分子 [√] 学校大课基金结题 英语六级未通过 国家专利未通过 【看完或者大部分看完的书籍】 [√] 《软件设计师考试》 [√] 《Python 编程从入门到实践》 [√] 《Python 编程从零基础到项目实战》 [√] 《Python3 网络爬虫开发实战》 [√] 《Python 网络爬虫从入门到实践》 [√] 《精通 Python 爬虫框架 Scrapy》 [√] 《Python 程序员面试宝典(算法+数据结构)》 [√] 《Selenium 自动化测试 — 基于 Python 语言》 [√] 《重构,改善既有代码的设计》 【生活方面】暑假受家族前辈的邀请,为整个姓氏家族编写族谱,感觉这是今年收获最大的一件事情吧,当时背着电脑跟着前辈下乡,挨家挨户统计资料,纯手工录入电脑(感觉那是我活了二十年打字打得最多的一个月,祖宗十八代都搞清楚了),最后排版打印成书,一个月下来感受到了信息化时代和传统文化的碰撞,见了很多古书,古迹,当然还领略到了古繁体字的魅力,前辈一路上给我讲述了很多书本上学不到的东西,一段很有意义的体验,感触颇深。 个人爱好上面,今年就基本上没有骑车了,没有经常骑车,开学骑了两次就跟不上别人了,后面就洗干净用布遮起来放在寝室了,按照目前情况来看,多半是要“退役”了,不知道何时才会又一次踩上脚踏,不过偶尔还是在抖音上刷刷关注的单车大佬,看看别人的视频,看到友链小伙伴 Shan San 在今年总结也写了他一年没有跳舞了,抛弃了曾经热爱的 Breaking,真的是深有感触啊。 有个遗憾就是大一的愿望实现不了了,恐怕大学四年也不会去西藏或者青海湖了,此处放一个到目前为止的骑行数据,以此纪念一下我的单车生涯吧。 【技术交流&实践】自从搭建了博客之后,认识了不少大佬,经常会去大佬博客逛逛,涨涨知识 截止目前,个人博客 PV:4万+,UV:1万+,知乎:400+赞同,CSDN:43万+访问量,400+赞同 此外今年第一次为开源做了一点儿微不足道的贡献,为 Hexo 博客主题 Material X 添加了文章字数统计和阅读时长的功能,提交了人生当中第一个 PR。第一次嘛,还是值得纪念一下的。 我 GitHub 上虽然有一些小绿点,但是很大一部分都是推送的博客相关的东西,剩下的有几个仓库也就是 Python 相关的了,一些实战的代码放在了上面,很多时候是拿 GitHub 围观一些牛逼代码或者资源,还需要努力学习啊! 实战方面,爬虫自己也爬了很多网站,遇到一些反爬网站还不能解决,也刷了一些 Checkio 上面的题,做了题,和其他大佬相比才会发现自己的代码水平有多低,最直接的感受就是我用了很多行代码,而大神一行代码就解决了,只能说自己的水平还有很大的增进空间,新的一年继续努力吧! 【2020】1024 + 996 = 2020,2020注定是不平凡的一年,定下目标,努力实现,只谈技术,莫问前程! 【计划目标】 4月蓝桥杯拿奖 5月通过软考高级信息系统项目管理师 6月通过英语六级 坚持记笔记、写博客 学习 JavaScript 逆向 研究网站常用反爬策略,掌握反反爬虫技术 掌握两到三个主流爬虫框架 加深 Python 算法和数据结构的学习 学习 Python 数据可视化和数据分析 做一个 Python 相关的优秀开源项目(爬虫类最好) 向优秀爬虫工程师方向迈进 参加 PyCon China 2020 【计划要看的书籍】 《JavaScript 从入门到精通》 《Python3 反爬虫原理与绕过实战》 《Python 数据可视化编程实战》 《Python 数据可视化之 matplotlib 实践》 《Python 数据可视化之 matplotlib 精进》 《基于 Python的大数据分析基础及实战》 123>>> pip uninstall 2019>>> pip install 2020>>> print('Live a good life, write some good code !!!')","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"年终总结","slug":"年终总结","permalink":"https://www.itrhx.com/tags/年终总结/"}]},{"title":"Python3 爬虫实战 — 瓜子全国二手车","slug":"A59-pyspider-guazi","date":"2019-11-14T16:10:55.649Z","updated":"2019-12-29T07:14:02.938Z","comments":true,"path":"2019/11/15/A59-pyspider-guazi/","link":"","permalink":"https://www.itrhx.com/2019/11/15/A59-pyspider-guazi/","excerpt":"爬取时间:2019-11-14爬取难度:★★☆☆☆☆请求链接:https://www.guazi.com/www/buy/爬取目标:爬取瓜子全国二手车信息,包括价格、上牌时间、表显里程等;保存车辆图片涉及知识:请求库 requests、解析库 lxml、Xpath 语法、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/guazi其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-11-14爬取难度:★★☆☆☆☆请求链接:https://www.guazi.com/www/buy/爬取目标:爬取瓜子全国二手车信息,包括价格、上牌时间、表显里程等;保存车辆图片涉及知识:请求库 requests、解析库 lxml、Xpath 语法、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/guazi其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】提取所有二手车详情页URL分析页面,按照习惯,最开始在 headers 里面只加入 User-Agent 字段,向主页发送请求,然而返回的东西并不是主页真正的源码,因此我们加入 Cookie,再次发起请求,即可得到真实数据。 获取 Cookie:打开浏览器访问网站,打开开发工具,切换到 Network 选项卡,筛选 Doc 文件,在 Request Headers 里可以看到 Cookie 值。 注意在爬取瓜子二手车的时候,User-Agent 与 Cookie 要对应一致,也就是直接复制 Request Headers 里的 User-Agent 和 Cookie,不要自己定义一个 User-Agent,不然有可能获取不到信息! 分析页面,请求地址为:https://www.guazi.com/www/buy/ 第一页:https://www.guazi.com/www/buy/ 第二页:https://www.guazi.com/www/buy/o2c-1/ 第三页:https://www.guazi.com/www/buy/o3c-1/ 一共有50页数据,利用 for 循环,每次改变 URL 中 o2c-1 参数里面的数字即可实现所有页面的爬取,由于我们是想爬取每台二手车详情页的数据,所以定义一个 parse_index() 函数,提取每一页的所有详情页的 URL,保存在列表 url_list 中 1234567891011121314151617181920# 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)headers = { 'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'}# 获取所有二手车详情页URLdef parse_index(): response = requests.get(url=url, headers=headers) tree = etree.HTML(response.text) url_list = tree.xpath('//li/a[@class=\"car-a\"]/@href') # print(len(url_list)) return url_listif __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() 【2x00】获取二手车详细信息并保存图片前面的第一步我们已经获取到了二手车详情页的 URL,现在定义一个 parse_detail() 函数,向其中循环传入每一条 URL,利用 Xpath 语法匹配每一条信息,所有信息包含:标题、二手车价格、新车指导价、车主、上牌时间、表显里程、上牌地、排放标准、变速箱、排量、过户次数、看车地点、年检到期、交强险、商业险到期。 其中有部分信息可能包含空格,可以用 strip() 方法将其去掉。 需要注意的是,上牌地对应的是一个 class="three" 的 li 标签,有些二手车没有上牌地信息,匹配的结果将是空,在数据储存时就有可能出现数组越界的错误信息,所以这里可以加一个判断,如果没有上牌地信息,可以将其赋值为:未知。 保存车辆图片时,为了节省时间和空间,避免频繁爬取被封,所以只保存第一张图片,同样利用 Xpath 匹配到第一张图片的地址,以标题为图片的名称,定义储存路径后,以二进制形式保存图片。 最后整个函数返回的是一个列表 data,这个列表包含每辆二手车的所有信息 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113# 获取二手车详细信息def parse_detail(content): detail_response = requests.get(url=content, headers=headers) tree = etree.HTML(detail_response.text) # 标题 title = tree.xpath('//h2[@class=\"titlebox\"]/text()') # 移除字符串头尾空格 title = [t.strip() for t in title] # 匹配到两个元素,只取其中一个为标题 title = title[:1] # print(title) # 价格 price_old = tree.xpath('//span[@class=\"pricestype\"]/text()') # 移除字符串头尾空格 price_old = [p.strip() for p in price_old] # 加入单位 price_old = [''.join(price_old + ['万'])] # print(price_old) # 新车指导价 price_new = tree.xpath('//span[@class=\"newcarprice\"]/text()') # 移除字符串头尾空格 price_new = [p.strip() for p in price_new] # 对字符串进行切片,只取数字多少万 price_new = ['¥' + price_new[0].split('价')[1]] # print(price_new) # 车主 owner = tree.xpath('//dl/dt/span/text()') owner = [owner[0].replace('车主:', '')] # print(owner) # 上牌时间 spsj = tree.xpath('//li[@class=\"one\"]/div/text()') # print(spsj) # 表显里程 bxlc = tree.xpath('//li[@class=\"two\"]/div/text()') # print(bxlc) # 上牌地 spd = tree.xpath('//li[@class=\"three\"]/div/text()') # 某些二手车没有上牌地,没有的将其赋值为:未知 if len(spd) == 0: spd = ['未知'] # print(spd) # 排放标准 pfbz = tree.xpath('//li[@class=\"four\"]/div/text()') pfbz = pfbz[:1] # print(pfbz) # 变速箱 bsx = tree.xpath('//li[@class=\"five\"]/div/text()') # print(bsx) # 排量 pl = tree.xpath('//li[@class=\"six\"]/div/text()') # print(pl) # 过户次数 ghcs = tree.xpath('//li[@class=\"seven\"]/div/text()') ghcs = [g.strip() for g in ghcs] ghcs = ghcs[:1] # print(ghcs) # 看车地点 kcdd = tree.xpath('//li[@class=\"eight\"]/div/text()') # print(kcdd) # 年检到期 njdq = tree.xpath('//li[@class=\"nine\"]/div/text()') # print(njdq) # 交强险 jqx = tree.xpath('//li[@class=\"ten\"]/div/text()') # print(jqx) # 商业险到期 syxdq = tree.xpath('//li[@class=\"last\"]/div/text()') syxdq = [s.strip() for s in syxdq] syxdq = syxdq[:1] # print(syxdq) # 保存车辆图片 # 获取图片地址 pic_url = tree.xpath('//li[@class=\"js-bigpic\"]/img/@data-src')[0] pic_response = requests.get(pic_url) # 定义图片名称以及保存的文件夹 pic_name = title[0] + '.jpg' dir_name = 'guazi_pic' # 如果没有该文件夹则创建该文件夹 if not os.path.exists(dir_name): os.mkdir(dir_name) # 定义储存路径 pic_path = dir_name + '/' + pic_name with open(pic_path, \"wb\")as f: f.write(pic_response.content) # 将每辆二手车的所有信息合并为一个列表 data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq return dataif __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) 【3x00】将数据储存到 MongoDB定义数据储存函数 save_data() 使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 guazi,集合为 esc 传入第二步 parse_detail() 函数返回的二手车信息的列表,依次读取其中的元素,每一个元素对应相应的信息名称 最后调用 insert_one() 方法,每次插入一辆二手车的数据 12345678910111213141516171819202122232425262728293031323334353637# 将数据储存到 MongoDBdef save_data(data): client = pymongo.MongoClient(host='localhost', port=27017) db = client.guazi collection = db.esc esc = { '标题': data[0], '二手车价格': data[1], '新车指导价': data[2], '车主': data[3], '上牌时间': data[4], '表显里程': data[5], '上牌地': data[6], '排放标准': data[7], '变速箱': data[8], '排量': data[9], '过户次数': data[10], '看车地点': data[11], '年检到期': data[12], '交强险': data[13], '商业险到期': data[14] } collection.insert_one(esc)if __name__ == '__main__': for i in range(1, 51): url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) save_data(car_data) # 在3-10秒之间随机暂停 time.sleep(random.randint(3, 10)) time.sleep(random.randint(5, 60)) print('所有数据爬取完毕!') 【4x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-11-14# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: guazi.py# @Software: PyCharm# =============================================from lxml import etreeimport requestsimport pymongoimport timeimport randomimport os# 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)headers = { 'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'}# 获取所有二手车详情页URLdef parse_index(): response = requests.get(url=url, headers=headers) tree = etree.HTML(response.text) url_list = tree.xpath('//li/a[@class=\"car-a\"]/@href') # print(len(url_list)) return url_list# 获取二手车详细信息def parse_detail(content): detail_response = requests.get(url=content, headers=headers) tree = etree.HTML(detail_response.text) # 标题 title = tree.xpath('//h2[@class=\"titlebox\"]/text()') # 移除字符串头尾空格 title = [t.strip() for t in title] # 匹配到两个元素,只取其中一个为标题 title = title[:1] # print(title) # 价格 price_old = tree.xpath('//span[@class=\"pricestype\"]/text()') # 移除字符串头尾空格 price_old = [p.strip() for p in price_old] # 加入单位 price_old = [''.join(price_old + ['万'])] # print(price_old) # 新车指导价 price_new = tree.xpath('//span[@class=\"newcarprice\"]/text()') # 移除字符串头尾空格 price_new = [p.strip() for p in price_new] # 对字符串进行切片,只取数字多少万 price_new = ['¥' + price_new[0].split('价')[1]] # print(price_new) # 车主 owner = tree.xpath('//dl/dt/span/text()') owner = [owner[0].replace('车主:', '')] # print(owner) # 上牌时间 spsj = tree.xpath('//li[@class=\"one\"]/div/text()') # print(spsj) # 表显里程 bxlc = tree.xpath('//li[@class=\"two\"]/div/text()') # print(bxlc) # 上牌地 spd = tree.xpath('//li[@class=\"three\"]/div/text()') # 某些二手车没有上牌地,没有的将其赋值为:未知 if len(spd) == 0: spd = ['未知'] # print(spd) # 排放标准 pfbz = tree.xpath('//li[@class=\"four\"]/div/text()') pfbz = pfbz[:1] # print(pfbz) # 变速箱 bsx = tree.xpath('//li[@class=\"five\"]/div/text()') # print(bsx) # 排量 pl = tree.xpath('//li[@class=\"six\"]/div/text()') # print(pl) # 过户次数 ghcs = tree.xpath('//li[@class=\"seven\"]/div/text()') ghcs = [g.strip() for g in ghcs] ghcs = ghcs[:1] # print(ghcs) # 看车地点 kcdd = tree.xpath('//li[@class=\"eight\"]/div/text()') # print(kcdd) # 年检到期 njdq = tree.xpath('//li[@class=\"nine\"]/div/text()') # print(njdq) # 交强险 jqx = tree.xpath('//li[@class=\"ten\"]/div/text()') # print(jqx) # 商业险到期 syxdq = tree.xpath('//li[@class=\"last\"]/div/text()') syxdq = [s.strip() for s in syxdq] syxdq = syxdq[:1] # print(syxdq) # 保存车辆图片 # 获取图片地址 pic_url = tree.xpath('//li[@class=\"js-bigpic\"]/img/@data-src')[0] pic_response = requests.get(pic_url) # 定义图片名称以及保存的文件夹 pic_name = title[0] + '.jpg' dir_name = 'guazi_pic' # 如果没有该文件夹则创建该文件夹 if not os.path.exists(dir_name): os.mkdir(dir_name) # 定义储存路径 pic_path = dir_name + '/' + pic_name with open(pic_path, \"wb\")as f: f.write(pic_response.content) # 将每辆二手车的所有信息合并为一个列表 data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq return data# 将数据储存到 MongoDBdef save_data(data): client = pymongo.MongoClient(host='localhost', port=27017) db = client.guazi collection = db.esc esc = { '标题': data[0], '二手车价格': data[1], '新车指导价': data[2], '车主': data[3], '上牌时间': data[4], '表显里程': data[5], '上牌地': data[6], '排放标准': data[7], '变速箱': data[8], '排量': data[9], '过户次数': data[10], '看车地点': data[11], '年检到期': data[12], '交强险': data[13], '商业险到期': data[14] } collection.insert_one(esc)if __name__ == '__main__': for i in range(1, 51): num = 0 print('正在爬取第' + str(i) + '页数据...') url = 'https://www.guazi.com/www/buy/o%sc-1/' % i detail_urls = parse_index() for detail_url in detail_urls: car_url = 'https://www.guazi.com' + detail_url car_data = parse_detail(car_url) save_data(car_data) num += 1 print('第' + str(num) + '条数据爬取完毕!') # 在3-10秒之间随机暂停 time.sleep(random.randint(3, 10)) print('第' + str(i) + '页数据爬取完毕!') print('=====================') time.sleep(random.randint(5, 60)) print('所有数据爬取完毕!') 【5x00】数据截图爬取的汽车图片: 储存到 MongoDB 的数据: 数据导出为 CSV 文件: 【6x00】程序不足的地方Cookie 过一段时间就会失效,数据还没爬取完就失效了,导致无法继续爬取;爬取效率不高,可以考虑多线程爬取","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"瓜子二手车","slug":"瓜子二手车","permalink":"https://www.itrhx.com/tags/瓜子二手车/"}]},{"title":"Python3 爬虫实战 — 58同城武汉出租房【加密字体对抗】","slug":"A58-pyspider-58tongcheng","date":"2019-10-21T13:22:53.980Z","updated":"2019-10-21T13:32:53.413Z","comments":true,"path":"2019/10/21/A58-pyspider-58tongcheng/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A58-pyspider-58tongcheng/","excerpt":"爬取时间:2019-10-21爬取难度:★★★☆☆☆请求链接:https://wh.58.com/chuzu/爬取目标:58同城武汉出租房的所有信息涉及知识:网站加密字体的攻克、请求库 requests、解析库 Beautiful Soup、数据库 MySQL 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/58tongcheng其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-21爬取难度:★★★☆☆☆请求链接:https://wh.58.com/chuzu/爬取目标:58同城武汉出租房的所有信息涉及知识:网站加密字体的攻克、请求库 requests、解析库 Beautiful Soup、数据库 MySQL 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/58tongcheng其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】加密字体攻克思路F12 打开调试模板,通过页面分析,可以观察到,网站里面凡是涉及到有数字的地方,都是显示为乱码,这种情况就是字体加密了,那么是通过什么手段实现字体加密的呢? CSS 中有一个 @font-face 规则,它允许为网页指定在线字体,也就是说可以引入自定义字体,这个规则本意是用来消除对电脑字体的依赖,现在不少网站也利用这个规则来实现反爬 右侧可以看到网站用的字体,其他的都是常见的微软雅黑,宋体等,但是有一个特殊的:fangchan-secret ,不难看出这应该就是58同城的自定义字体了 我们通过控制台看到的乱码事实上是由于 unicode 编码导致,查看网页源代码,我们才能看到他真正的编码信息 要攻克加密字体,那么我们肯定要分析他的字体文件了,先想办法得到他的加密字体文件,同样查看源代码,在源代码中搜索 fangchan-secret 的字体信息 选中的蓝色部分就是 base64 编码的加密字体字符串了,我们将其解码成二进制编码,写进 .woff 的字体文件,这个过程可以通过以下代码实现: 1234567891011121314151617import requestsimport base64headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}url = 'https://wh.58.com/chuzu/'response = requests.get(url=url, headers=headers)# 匹配 base64 编码的加密字体字符串base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip()# 将 base64 编码的字体字符串解码成二进制编码bin_data = base64.decodebytes(base64_string.encode())# 保存为字体文件with open('58font.woff', 'wb') as f: f.write(bin_data) 得到字体文件后,我们可以通过 FontCreator 这个软件来看看字体对应的编码是什么: 观察我们在网页源代码中看到的编码:类似于 &#x9fa4;、&#x9f92; 对比字体文件对应的编码:类似于 uni9FA4、nui9F92 可以看到除了前面三个字符不一样以外,后面的字符都是一样的,只不过英文大小写有所差异 现在我们可能会想到,直接把编码替换成对应的数字不就OK了?然而并没有这么简单 尝试刷新一下网页,可以观察到 base64 编码的加密字体字符串会改变,也就是说编码和数字并不是一一对应的,再次获取几个字体文件,通过对比就可以看出来 可以看到,虽然每次数字对应的编码都不一样,但是编码总是这10个,是不变的,那么编码与数字之间肯定存在某种对应关系,,我们可以将字体文件转换为 xml 文件来观察其中的对应关系,改进原来的代码即可实现转换功能: 123456789101112131415161718192021import requestsimport base64from fontTools.ttLib import TTFontheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}url = 'https://wh.58.com/chuzu/'response = requests.get(url=url, headers=headers)# 匹配 base64 编码的加密字体字符串base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip()# 将 base64 编码的字体字符串解码成二进制编码bin_data = base64.decodebytes(base64_string.encode())# 保存为字体文件with open('58font.woff', 'wb') as f: f.write(bin_data)# 获取字体文件,将其转换为xml文件font = TTFont('58font.woff')font.saveXML('58font.xml') 打开 58font.xml 文件并分析,在 <cmap> 标签内可以看到熟悉的类似于 0x9476、0x958f 的编码,其后四位字符恰好是网页字体的加密编码,可以看到每一个编码后面都对应了一个 glyph 开头的编码 将其与 58font.woff 文件对比,可以看到 code 为 0x958f 这个编码对应的是数字 3,对应的 name 编码是 glyph00004 我们再次获取一个字体文件作为对比分析 依然是 0x958f 这个编码,两次对应的 name 分别是 glyph00004 和 glyph00007,两次对应的数字分别是 3 和 6,那么结论就来了,每次发送请求,code 对应的 name 会随机发生变化,而 name 对应的数字不会发生变化,glyph00001 对应数字 0、glyph00002 对应数字 1,以此类推 那么以 glyph 开头的编码是如何对应相应的数字的呢?在 xml 文件里面,每个编码都有一个 TTGlyph 的标签,标签里面是一行一行的类似于 x,y 坐标的东西,这个其实就是用来绘制字体的,用 matplotlib 根据坐标画个图,就可以看到是一个数字 此时,我们就知道了编码与数字的对应关系,下一步,我们可以查找 xml 文件里,编码对应的 name 的值,也就是以 glyph 开头的编码,然后返回其对应的数字,再替换掉网页源代码里的编码,就能成功获取到我们需要的信息了! 总结一下攻克加密字体的大致思路: 分析网页,找到对应的加密字体文件 如果引用的加密字体是一个 base64 编码的字符串,则需要转换成二进制并保存到 woff 字体文件中 将字体文件转换成 xml 文件 用 FontCreator 软件观察字体文件,结合 xml 文件,分析其编码与真实字体的关系 搞清楚编码与字体的关系后,想办法将编码替换成正常字体 【2x00】思维导图 【3x00】加密字体处理模块【3x01】获取字体文件并转换为xml文件12345678910111213141516def get_font(page_url, page_num): response = requests.get(url=page_url, headers=headers) # 匹配 base64 编码的加密字体字符串 base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip() # print(base64_string) # 将 base64 编码的字体字符串解码成二进制编码 bin_data = base64.decodebytes(base64_string.encode()) # 保存为字体文件 with open('58font.woff', 'wb') as f: f.write(bin_data) print('第' + str(page_num) + '次访问网页,字体文件保存成功!') # 获取字体文件,将其转换为xml文件 font = TTFont('58font.woff') font.saveXML('58font.xml') print('已成功将字体文件转换为xml文件!') return response.text 由主函数传入要发送请求的 url,利用字符串的 split() 方法,匹配 base64 编码的加密字体字符串,利用 base64 模块的 base64.decodebytes() 方法,将 base64 编码的字体字符串解码成二进制编码并保存为字体文件,利用 FontTools 库,将字体文件转换为 xml 文件 【3x02】将加密字体编码与真实字体进行匹配12345678910111213141516171819202122232425262728293031def find_font(): # 以glyph开头的编码对应的数字 glyph_list = { 'glyph00001': '0', 'glyph00002': '1', 'glyph00003': '2', 'glyph00004': '3', 'glyph00005': '4', 'glyph00006': '5', 'glyph00007': '6', 'glyph00008': '7', 'glyph00009': '8', 'glyph00010': '9' } # 十个加密字体编码 unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5'] num_list = [] # 利用xpath语法匹配xml文件内容 font_data = etree.parse('./58font.xml') for unicode in unicode_list: # 依次循环查找xml文件里code对应的name result = font_data.xpath(\"//cmap//map[@code='{}']/@name\".format(unicode))[0] # print(result) # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value for key in glyph_list.keys(): if key == result: num_list.append(glyph_list[key]) print('已成功找到编码所对应的数字!') # print(num_list) # 返回value列表 return num_list 由前面的分析,我们知道 name 的值(即以 glyph 开头的编码)对应的数字是固定的,glyph00001 对应数字 0、glyph00002 对应数字 1,以此类推,所以可以将其构造成为一个字典 glyph_list 同样将十个 code(即类似于 0x9476 的加密字体编码)构造成一个列表 循环查找这十个 code 在 xml 文件里对应的 name 的值,然后将 name 的值与字典文件的 key 值进行对比,如果两者值相同,则获取这个 key 的 value 值,最终得到的列表 num_list,里面的元素就是 unicode_list 列表里面每个加密字体的真实值 【3x03】替换掉网页中所有的加密字体编码12345def replace_font(num, page_response): # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5 result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9]) print('已成功将所有加密字体替换!') return result 传入由上一步 find_font() 函数得到的真实字体的列表,利用 replace() 方法,依次将十个加密字体编码替换掉 【4x00】租房信息提取模块1234567891011121314151617181920212223242526272829303132333435363738def parse_pages(pages): num = 0 soup = BeautifulSoup(pages, 'lxml') # 查找到包含所有租房的li标签 all_house = soup.find_all('li', class_='house-cell') for house in all_house: # 标题 title = house.find('a', class_='strongbox').text.strip() # print(title) # 价格 price = house.find('div', class_='money').text.strip() # print(price) # 户型和面积 layout = house.find('p', class_='room').text.replace(' ', '') # print(layout) # 楼盘和地址 address = house.find('p', class_='infor').text.replace(' ', '').replace('\\n', '') # print(address) # 如果存在经纪人 if house.find('div', class_='jjr'): agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\\n', '') # 如果存在品牌公寓 elif house.find('p', class_='gongyu'): agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\\n', '') # 如果存在个人房源 else: agent = house.find('p', class_='geren').text.replace(' ', '').replace('\\n', '') # print(agent) data = [title, price, layout, address, agent] save_to_mysql(data) num += 1 print('第' + str(num) + '条数据爬取完毕,暂停3秒!') time.sleep(3) 利用 BeautifulSoup 解析库很容易提取到相关信息,这里要注意的是,租房信息来源分为三种:经纪人、品牌公寓和个人房源,这三个的元素节点也不一样,因此匹配的时候要注意 【5x00】MySQL数据储存模块【5x01】创建MySQL数据库的表123456def create_mysql_table(): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)' cursor.execute(sql) db.close() 首先指定数据库为 58tc_spiders,需要事先使用 MySQL 语句创建,也可以通过 MySQL Workbench 手动创建 然后使用 SQL 语句创建 一个表:58tc_data,表中包含 title、price、layout、address、agent 五个字段,类型都为 varchar 此创建表的操作也可以事先手动创建,手动创建后就不需要此函数了 【5x02】将数据储存到MySQL数据库12345678910def save_to_mysql(data): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)' try: cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4])) db.commit() except: db.rollback() db.close() commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏 【6x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: 58tongcheng.py# @Software: PyCharm# =============================================import requestsimport timeimport randomimport base64import pymysqlfrom lxml import etreefrom bs4 import BeautifulSoupfrom fontTools.ttLib import TTFontheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}# 获取字体文件并转换为xml文件def get_font(page_url, page_num): response = requests.get(url=page_url, headers=headers) # 匹配 base64 编码的加密字体字符串 base64_string = response.text.split(\"base64,\")[1].split(\"'\")[0].strip() # print(base64_string) # 将 base64 编码的字体字符串解码成二进制编码 bin_data = base64.decodebytes(base64_string.encode()) # 保存为字体文件 with open('58font.woff', 'wb') as f: f.write(bin_data) print('第' + str(page_num) + '次访问网页,字体文件保存成功!') # 获取字体文件,将其转换为xml文件 font = TTFont('58font.woff') font.saveXML('58font.xml') print('已成功将字体文件转换为xml文件!') return response.text# 将加密字体编码与真实字体进行匹配def find_font(): # 以glyph开头的编码对应的数字 glyph_list = { 'glyph00001': '0', 'glyph00002': '1', 'glyph00003': '2', 'glyph00004': '3', 'glyph00005': '4', 'glyph00006': '5', 'glyph00007': '6', 'glyph00008': '7', 'glyph00009': '8', 'glyph00010': '9' } # 十个加密字体编码 unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5'] num_list = [] # 利用xpath语法匹配xml文件内容 font_data = etree.parse('./58font.xml') for unicode in unicode_list: # 依次循环查找xml文件里code对应的name result = font_data.xpath(\"//cmap//map[@code='{}']/@name\".format(unicode))[0] # print(result) # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value for key in glyph_list.keys(): if key == result: num_list.append(glyph_list[key]) print('已成功找到编码所对应的数字!') # print(num_list) # 返回value列表 return num_list# 替换掉网页中所有的加密字体编码def replace_font(num, page_response): # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5 result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9]) print('已成功将所有加密字体替换!') return result# 提取租房信息def parse_pages(pages): num = 0 soup = BeautifulSoup(pages, 'lxml') # 查找到包含所有租房的li标签 all_house = soup.find_all('li', class_='house-cell') for house in all_house: # 标题 title = house.find('a', class_='strongbox').text.strip() # print(title) # 价格 price = house.find('div', class_='money').text.strip() # print(price) # 户型和面积 layout = house.find('p', class_='room').text.replace(' ', '') # print(layout) # 楼盘和地址 address = house.find('p', class_='infor').text.replace(' ', '').replace('\\n', '') # print(address) # 如果存在经纪人 if house.find('div', class_='jjr'): agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\\n', '') # 如果存在品牌公寓 elif house.find('p', class_='gongyu'): agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\\n', '') # 如果存在个人房源 else: agent = house.find('p', class_='geren').text.replace(' ', '').replace('\\n', '') # print(agent) data = [title, price, layout, address, agent] save_to_mysql(data) num += 1 print('第' + str(num) + '条数据爬取完毕,暂停3秒!') time.sleep(3)# 创建MySQL数据库的表:58tc_datadef create_mysql_table(): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)' cursor.execute(sql) db.close()# 将数据储存到MySQL数据库def save_to_mysql(data): db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders') cursor = db.cursor() sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)' try: cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4])) db.commit() except: db.rollback() db.close()if __name__ == '__main__': create_mysql_table() print('MySQL表58tc_data创建成功!') for i in range(1, 71): url = 'https://wh.58.com/chuzu/pn' + str(i) + '/' response = get_font(url, i) num_list = find_font() pro_pages = replace_font(num_list, response) parse_pages(pro_pages) print('第' + str(i) + '页数据爬取完毕!') time.sleep(random.randint(3, 60)) print('所有数据爬取完毕!') 【7x00】数据截图","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"58同城","slug":"58同城","permalink":"https://www.itrhx.com/tags/58同城/"}]},{"title":"Python3 爬虫实战 — 模拟登陆12306【点触验证码对抗】","slug":"A57-pyspider-12306-login","date":"2019-10-21T08:41:50.349Z","updated":"2019-10-21T13:33:47.617Z","comments":true,"path":"2019/10/21/A57-pyspider-12306-login/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A57-pyspider-12306-login/","excerpt":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://kyfw.12306.cn/otn/resources/login.html实现目标:模拟登陆中国铁路12306,攻克点触验证码涉及知识:点触验证码的攻克、自动化测试工具 Selenium 的使用、对接在线打码平台完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/12306-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://kyfw.12306.cn/otn/resources/login.html实现目标:模拟登陆中国铁路12306,攻克点触验证码涉及知识:点触验证码的攻克、自动化测试工具 Selenium 的使用、对接在线打码平台完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/12306-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】思维导图 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证 发送请求,出现验证码后,剪裁并保存验证码图片 选择在线打码平台,获取其API,以字节流格式发送图片 打码平台人工识别验证码,返回验证码的坐标信息 解析返回的坐标信息,模拟点击验证码,完成验证后点击登陆 【2x00】打码平台选择关于打码平台:在线打码平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多,本次实战使用超级鹰打码平台 使用打码平台:在超级鹰打码平台注册账号,官网:http://www.chaojiying.com/ ,充值一块钱得到 1000 题分,在用户中心里面申请一个软件 ID ,在价格体系里面确定验证码的类型,先观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1 至 4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 然后在开发文档里面获取其 Python API,下载下来以备后用 【3x00】初始化模块【3x01】初始化函数1234567891011121314151617181920212223242526# 12306账号密码USERNAME = '155********'PASSWORD = '***********'# 超级鹰打码平台账号密码CHAOJIYING_USERNAME = '*******'CHAOJIYING_PASSWORD = '*******'# 超级鹰打码平台软件IDCHAOJIYING_SOFT_ID = '********'# 验证码类型CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.username = USERNAME self.password = PASSWORD self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) 定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录页面 url ,谷歌浏览器驱动的目录(path),浏览器启动参数等,将超级鹰账号密码等相关参数传递给超级鹰 API 【3x02】账号密码输入函数12345678910111213def get_input_element(self): # 登录页面发送请求 self.browser.get(self.url) # 登录页面默认是扫码登录,所以首先要点击账号登录 login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) # 查找到账号密码输入位置的元素 username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) # 输入账号密码 username.send_keys(self.username) password.send_keys(self.password) 分析页面可知,登陆页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userName 和 J-password,调用 send_keys() 方法输入账号密码 【4x00】验证码处理模块1234567891011121314151617181920212223242526def crack(self): # 调用账号密码输入函数 self.get_input_element() # 调用验证码图片剪裁函数 image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) # 调用验证码坐标解析函数 locations = self.get_points(result) # 调用模拟点击验证码函数 self.touch_click_words(locations) # 调用模拟点击登录函数 self.login() try: # 查找是否出现用户的姓名,若出现表示登录成功 success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print('用户' + cc.text + '登录成功') # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值 except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() crack() 为验证码处理模块的主函数 调用账号密码输入函数 get_input_element(),等待账号密码输入完毕 调用验证码图片剪裁函数 get_touclick_image(),得到验证码图片 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON,如果识别成功,典型的返回结果类似于: 12{'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': '1f8e1d4bef8b11484cb1f1f34299865b'} 其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔 调用 get_points() 函数解析超级鹰识别结果 调用 touch_click_words() 函数对符合要求的图片进行点击 调用模拟点击登录函数 login(),点击登陆按钮模拟登陆 使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值 【4x01】验证码图片剪裁函数1234567891011121314def get_touclick_image(self, name='12306.png'): # 获取验证码的位置 element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] # 先对整个页面截图 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) # 根据验证码坐标信息,剪裁出验证码图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha 首先查找到验证码的坐标信息,先对整个页面截图,然后根据验证码坐标信息,剪裁出验证码图片 location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 【4x02】验证码坐标解析函数123456def get_points(self, captcha_result): # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔 groups = captcha_result.get('pic_str').split('|') # 将坐标信息变成列表的形式 locations = [[int(number) for number in group.split(',')] for group in groups] return locations get_points() 方法将超级鹰的验证码识别结果变成列表的形式 【4x03】模拟点击验证码函数123456def touch_click_words(self, locations): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) # 循环点击正确验证码的坐标 for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform() 循环提取正确的验证码坐标信息,依次点击验证码 【5x00】登录模块123def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click() 分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录 【6x00】完整代码【6x01】12306.py123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: 12306.py# @Software: PyCharm# =============================================import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import ChaojiyingClientfrom selenium.common.exceptions import TimeoutException# 12306账号密码USERNAME = '155********'PASSWORD = '***********'# 超级鹰打码平台账号密码CHAOJIYING_USERNAME = '********'CHAOJIYING_PASSWORD = '********'# 超级鹰打码平台软件IDCHAOJIYING_SOFT_ID = '******'# 验证码类型CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.username = USERNAME self.password = PASSWORD self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) def crack(self): # 调用账号密码输入函数 self.get_input_element() # 调用验证码图片剪裁函数 image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) # 调用验证码坐标解析函数 locations = self.get_points(result) # 调用模拟点击验证码函数 self.touch_click_words(locations) # 调用模拟点击登录函数 self.login() try: # 查找是否出现用户的姓名,若出现表示登录成功 success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print('用户' + cc.text + '登录成功') # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值 except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() # 账号密码输入函数 def get_input_element(self): # 登录页面发送请求 self.browser.get(self.url) # 登录页面默认是扫码登录,所以首先要点击账号登录 login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) # 查找到账号密码输入位置的元素 username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) # 输入账号密码 username.send_keys(self.username) password.send_keys(self.password) # 验证码图片剪裁函数 def get_touclick_image(self, name='12306.png'): # 获取验证码的位置 element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[ 'width'] # 先对整个页面截图 screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) # 根据验证码坐标信息,剪裁出验证码图片 captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha # 验证码坐标解析函数,分析超级鹰返回的坐标 def get_points(self, captcha_result): # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔 groups = captcha_result.get('pic_str').split('|') # 将坐标信息变成列表的形式 locations = [[int(number) for number in group.split(',')] for group in groups] return locations # 模拟点击验证码函数 def touch_click_words(self, locations): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) # 循环点击正确验证码的坐标 for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform() # 模拟点击登录函数 def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click()if __name__ == '__main__': crack = CrackTouClick() crack.crack() 【6x02】chaojiying.py12345678910111213141516171819202122232425262728293031323334353637383940414243import requestsfrom hashlib import md5class ChaojiyingClient(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): \"\"\" im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html \"\"\" params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): \"\"\" im_id:报错题目的图片ID \"\"\" params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json() 【7x00】效果实现动图最终实现效果图:(关键信息已经过打码处理)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"12306","slug":"12306","permalink":"https://www.itrhx.com/tags/12306/"}]},{"title":"Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】","slug":"A56-pyspider-bilibili-login","date":"2019-10-21T04:26:46.838Z","updated":"2019-10-21T13:33:57.637Z","comments":true,"path":"2019/10/21/A56-pyspider-bilibili-login/","link":"","permalink":"https://www.itrhx.com/2019/10/21/A56-pyspider-bilibili-login/","excerpt":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://passport.bilibili.com/login实现目标:模拟登陆哔哩哔哩,攻克滑动验证码涉及知识:滑动验证码的攻克、自动化测试工具 Selenium 的使用完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/bilibili-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"登陆时间:2019-10-21实现难度:★★★☆☆☆请求链接:https://passport.bilibili.com/login实现目标:模拟登陆哔哩哔哩,攻克滑动验证码涉及知识:滑动验证码的攻克、自动化测试工具 Selenium 的使用完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/bilibili-login其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】思维导图 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证 分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片 对比原始的图片和带缺口的图片的像素,像素不同的地方就是缺口位置 计算出滑块缺口的位置,得到所需要滑动的距离 拖拽时要模仿人的行为,由于有个对准过程,所以要构造先快后慢的运动轨迹 最后利用 Selenium 进行对滑块的拖拽 【2x00】登陆模块【2x01】初始化函数12345678910111213def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) # 你的哔哩哔哩用户名 username = '155********' # 你的哔哩哔哩登陆密码 password = '***********' wait = WebDriverWait(browser, 20) global 关键字定义了发起请求的url、用户名、密码等全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时 【2x02】登陆函数12345678910111213141516def login(): browser.get(url) # 获取用户名输入框 user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) # 获取密码输入框 passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) # 输入用户名 user.send_keys(username) # 输入密码 passwd.send_keys(password) # 获取登录按钮 login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) # 随机暂停几秒 time.sleep(random.random() * 3) # 点击登陆按钮 login_btn.click() 等待用户名输入框和密码输入框对应的 ID 节点加载出来 获取这两个节点,用户名输入框 id="login-username",密码输入框 id="login-passwd" 调用 send_keys() 方法输入用户名和密码 获取登录按钮 class="btn btn-login" 随机产生一个数并将其扩大三倍作为暂停时间 最后调用 click() 方法实现登录按钮的点击 【3x00】验证码处理模块【3x01】验证码元素查找函数12345678910111213141516171819202122def find_element(): # 获取带有缺口的图片 c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) # 获取需要滑动的图片 c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) # 获取完整的图片 c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) # 隐藏需要滑动的图片 hide_element(c_slice) # 保存带有缺口的图片 save_screenshot(c_background, 'back') # 显示需要滑动的图片 show_element(c_slice) # 保存需要滑动的图片 save_screenshot(c_slice, 'slice') # 显示完整的图片 show_element(c_full_bg) # 保存完整的图片 save_screenshot(c_full_bg, 'full') 获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片 分析页面代码,三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片 定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute 最后传值给 save_screenshot() 函数,进一步对验证码进行处理 【3x02】元素可见性设置函数12345678# 设置元素不可见def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")# 设置元素可见def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\") 【3x03】验证码截图函数123456789101112131415161718192021222324def save_screenshot(obj, name): try: # 首先对出现验证码后的整个页面进行截图保存 pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) # 计算传入的obj,也就是三张图片的位置信息 left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] # 打印输出一下每一张图的位置信息 print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存 im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增 size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 首先调用 save_screenshot() 属性对整个页面截图并保存 然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存 【4x00】验证码滑动模块【4x01】滑动主函数123456def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3) 向 get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证 【4x02】缺口位置寻找函数123456789101112def is_pixel_equal(bg_image, fullbg_image, x, y): # 获取两张图片对应像素点的RGB数据 bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] # 设定一个阈值 threshold = 60 # 比较两张图 RGB 的绝对值是否均小于定义的阈值 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False 将完整图片和缺口图片两个对象分别赋值给变量 bg_image 和 fullbg_image,接下来对比图片获取缺口。遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置 【4x03】计算滑块移动距离函数123456789def get_distance(bg_image, fullbg_image): # 滑块的初始位置 distance = 60 # 遍历两张图片的每个像素 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): # 调用缺口位置寻找函数 if not is_pixel_equal(fullbg_image, bg_image, i, j): return i get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 缺口位置寻找函数判断两张图片同一位置的像素是否相同,若不相同则返回该点的值 【4x04】构造移动轨迹函数1234567891011121314151617181920def get_trace(distance): trace = [] # 设置加速距离为总距离的4/5 faster_distance = distance * (4 / 5) # 设置初始位置、初始速度、时间间隔 start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 10 else: a = -10 # 位移 move = v0 * t + 1 / 2 * a * t * t # 当前时刻的速度 v = v0 + a * t v0 = v start += move trace.append(round(move)) # trace 记录了每个时间间隔移动了多少位移 return trace get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系: 12move = v0 * t + 0.5 * a * t * t v = v0 + a * t 设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为10和-10,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了 【4x05】模拟拖动函数123456789101112def move_to_gap(trace): # 获取滑动按钮 slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) # 点击并拖动滑块 ActionChains(browser).click_and_hold(slider).perform() # 遍历运动轨迹获取每小段位移距离 for x in trace: # 移动此位移 ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) # 释放鼠标 ActionChains(browser).release().perform() 传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-21# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: bilibili.py# @Software: PyCharm# =============================================from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport randomfrom PIL import Image# 初始化函数def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径 path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) # 你的哔哩哔哩用户名 username = '155********' # 你的哔哩哔哩登录密码 password = '***********' wait = WebDriverWait(browser, 20)# 登录函数def login(): browser.get(url) # 获取用户名输入框 user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) # 获取密码输入框 passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) # 输入用户名 user.send_keys(username) # 输入密码 passwd.send_keys(password) # 获取登录按钮 login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) # 随机暂停几秒 time.sleep(random.random() * 3) # 点击登陆按钮 login_btn.click()# 验证码元素查找函数def find_element(): # 获取带有缺口的图片 c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) # 获取需要滑动的图片 c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) # 获取完整的图片 c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) # 隐藏需要滑动的图片 hide_element(c_slice) # 保存带有缺口的图片 save_screenshot(c_background, 'back') # 显示需要滑动的图片 show_element(c_slice) # 保存需要滑动的图片 save_screenshot(c_slice, 'slice') # 显示完整的图片 show_element(c_full_bg) # 保存完整的图片 save_screenshot(c_full_bg, 'full')# 设置元素不可见def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")# 设置元素可见def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\")# 验证码截图函数def save_screenshot(obj, name): try: # 首先对出现验证码后的整个页面进行截图保存 pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) # 计算传入的obj,也就是三张图片的位置信息 left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] # 打印输出一下每一张图的位置信息 print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存 im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg)# 滑动模块的主函数def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3)# 计算滑块移动距离函数def get_distance(bg_image, fullbg_image): # 滑块的初始位置 distance = 60 # 遍历两张图片的每个像素 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): # 调用缺口位置寻找函数 if not is_pixel_equal(fullbg_image, bg_image, i, j): return i# 缺口位置寻找函数def is_pixel_equal(bg_image, fullbg_image, x, y): # 获取两张图片对应像素点的RGB数据 bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] # 设定一个阈值 threshold = 60 # 比较两张图 RGB 的绝对值是否均小于定义的阈值 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False# 构造移动轨迹函数def get_trace(distance): trace = [] # 设置加速距离为总距离的4/5 faster_distance = distance * (4 / 5) # 设置初始位置、初始速度、时间间隔 start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 10 else: a = -10 # 位移 move = v0 * t + 1 / 2 * a * t * t # 当前时刻的速度 v = v0 + a * t v0 = v start += move trace.append(round(move)) # trace 记录了每个时间间隔移动了多少位移 return trace# 模拟拖动函数def move_to_gap(trace): # 获取滑动按钮 slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) # 点击并拖动滑块 ActionChains(browser).click_and_hold(slider).perform() # 遍历运动轨迹获取每小段位移距离 for x in trace: # 移动此位移 ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) # 释放鼠标 ActionChains(browser).release().perform()if __name__ == '__main__': init() login() find_element() slide() 【6x00】效果实现动图最终实现效果图:(关键信息已经过打码处理)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"哔哩哔哩","slug":"哔哩哔哩","permalink":"https://www.itrhx.com/tags/哔哩哔哩/"}]},{"title":"Python3 爬虫实战 — 虎扑论坛步行街","slug":"A55-pyspider-hupu","date":"2019-10-12T15:28:23.380Z","updated":"2019-10-21T04:08:46.598Z","comments":true,"path":"2019/10/12/A55-pyspider-hupu/","link":"","permalink":"https://www.itrhx.com/2019/10/12/A55-pyspider-hupu/","excerpt":"爬取时间:2019-10-12爬取难度:★★☆☆☆☆请求链接:https://bbs.hupu.com/bxj爬取目标:爬取虎扑论坛步行街的帖子,包含主题,作者,发布时间等,数据保存到 MongoDB 数据库涉及知识:请求库 requests、解析库 Beautiful Soup、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/hupu其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-12爬取难度:★★☆☆☆☆请求链接:https://bbs.hupu.com/bxj爬取目标:爬取虎扑论坛步行街的帖子,包含主题,作者,发布时间等,数据保存到 MongoDB 数据库涉及知识:请求库 requests、解析库 Beautiful Soup、数据库 MongoDB 的操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/hupu其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察虎扑论坛步行街分区,请求地址为:https://bbs.hupu.com/bxj 第一页:https://bbs.hupu.com/bxj 第二页:https://bbs.hupu.com/bxj-2 第三页:https://bbs.hupu.com/bxj-3 不难发现,每增加一页,只需要添加 -页数 参数即可,最后一页是第 50 页,因此可以利用 for 循环依次爬取,定义一个 get_pages() 函数,返回初始化 Beautiful Soup 的对象 page_soup,方便后面的解析函数调用 虽然一共有 50 页,但是当用户访问第 10 页以后的页面的时候,会要求登录虎扑,不然就没法查看,而且登录时会出现智能验证,所以程序只爬取前 10 页的数据 123456789101112def get_pages(page_url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } response = requests.get(url=page_url, headers=headers) page_soup = BeautifulSoup(response.text, 'lxml') return page_soupif __name__ == '__main__': for i in range(1, 11): url = 'https://bbs.hupu.com/bxj-' + str(i) soup = get_pages(url) 【2x00】解析模块使用 Beautiful Soup 对网页各个信息进行提取,最后将这些信息放进一个列表里,然后调用列表的 .append() 方法,再将每条帖子的列表依次加到另一个新列表里,最终返回的是类似于如下形式的列表: 1[['帖子1', '作者1'], ['帖子2', '作者2'], ['帖子3', '作者3']] 这样做的目的是:方便 MongoDB 依次储存每一条帖子的信息 123456789101112131415161718192021222324252627282930313233343536373839def parse_pages(page_soup): data_list = [] all_list = page_soup.find('ul', class_='for-list') post_list = all_list.find_all('li') # print(result_list) for post in post_list: # 帖子名称 post_title = post.find('a', class_='truetit').text # print(post_title) # 帖子链接 post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href'] # print(post_url) # 作者 author = post.select('.author > a')[0].text # print(author) # 作者主页 author_url = post.select('.author > a')[0]['href'] # print(author_url) # 发布日期 post_date = post.select('.author > a')[1].text # print(post_date) reply_view = post.find('span', class_='ansour').text # 回复数 post_reply = reply_view.split('/')[0].strip() # print(post_reply) # 浏览量 post_view = reply_view.split('/')[1].strip() # print(post_view) # 最后回复时间 last_data = post.select('.endreply > a')[0].text # print(last_data) # 最后回复用户 last_user = post.select('.endreply > span')[0].text # print(last_user) data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user]) # print(data_list) return data_list 【3x00】MongoDB 数据储存模块首先使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 hupu,集合为 bxj 将解析函数返回的列表传入到储存函数,依次循环该列表,对每一条帖子的信息进行提取并储存 1234567891011121314151617def mongodb(data_list): client = MongoClient('localhost', 27017) db = client.hupu collection = db.bxj for data in data_list: bxj = { '帖子名称': data[0], '帖子链接': data[1], '作者': data[2], '作者主页': data[3], '发布日期': str(data[4]), '回复数': data[5], '浏览量': data[6], '最后回复时间': str(data[7]), '最后回复用户': data[8] } collection.insert_one(bxj) 【4x00】完整代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-12# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: hupu.py# @Software: PyCharm# =============================================import requestsimport timeimport randomfrom pymongo import MongoClientfrom bs4 import BeautifulSoupdef get_pages(page_url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36' } response = requests.get(url=page_url, headers=headers) page_soup = BeautifulSoup(response.text, 'lxml') return page_soupdef parse_pages(page_soup): data_list = [] all_list = page_soup.find('ul', class_='for-list') post_list = all_list.find_all('li') # print(result_list) for post in post_list: # 帖子名称 post_title = post.find('a', class_='truetit').text # print(post_title) # 帖子链接 post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href'] # print(post_url) # 作者 author = post.select('.author > a')[0].text # print(author) # 作者主页 author_url = post.select('.author > a')[0]['href'] # print(author_url) # 发布日期 post_date = post.select('.author > a')[1].text # print(post_date) reply_view = post.find('span', class_='ansour').text # 回复数 post_reply = reply_view.split('/')[0].strip() # print(post_reply) # 浏览量 post_view = reply_view.split('/')[1].strip() # print(post_view) # 最后回复时间 last_data = post.select('.endreply > a')[0].text # print(last_data) # 最后回复用户 last_user = post.select('.endreply > span')[0].text # print(last_user) data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user]) # print(data_list) return data_listdef mongodb(data_list): client = MongoClient('localhost', 27017) db = client.hupu collection = db.bxj for data in data_list: bxj = { '帖子名称': data[0], '帖子链接': data[1], '作者': data[2], '作者主页': data[3], '发布日期': str(data[4]), '回复数': data[5], '浏览量': data[6], '最后回复时间': str(data[7]), '最后回复用户': data[8] } collection.insert_one(bxj)if __name__ == '__main__': for i in range(1, 11): url = 'https://bbs.hupu.com/bxj-' + str(i) soup = get_pages(url) result_list = parse_pages(soup) mongodb(result_list) print('第', i, '页数据爬取完毕!') time.sleep(random.randint(3, 10)) print('前10页所有数据爬取完毕!') 【5x00】数据截图一共爬取到 1180 条数据: 【6x00】程序不足的地方程序只能爬取前 10 页的数据,因为虎扑论坛要求从第 11 页开始,必须登录账号才能查看,并且登录时会有智能验证,可以使用自动化测试工具 Selenium 模拟登录账号后再进行爬取。","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"虎扑论坛","slug":"虎扑论坛","permalink":"https://www.itrhx.com/tags/虎扑论坛/"}]},{"title":"Python3 爬虫实战 — 安居客武汉二手房","slug":"A54-pyspider-anjuke","date":"2019-10-09T15:02:42.994Z","updated":"2019-10-21T04:05:48.158Z","comments":true,"path":"2019/10/09/A54-pyspider-anjuke/","link":"","permalink":"https://www.itrhx.com/2019/10/09/A54-pyspider-anjuke/","excerpt":"爬取时间:2019-10-09爬取难度:★★☆☆☆☆请求链接:https://wuhan.anjuke.com/sale/爬取目标:爬取武汉二手房每一条售房信息,包含地理位置、价格、面积等,保存为 CSV 文件涉及知识:请求库 requests、解析库 Beautiful Soup、CSV 文件储存、列表操作、分页判断完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/anjuke其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-10-09爬取难度:★★☆☆☆☆请求链接:https://wuhan.anjuke.com/sale/爬取目标:爬取武汉二手房每一条售房信息,包含地理位置、价格、面积等,保存为 CSV 文件涉及知识:请求库 requests、解析库 Beautiful Soup、CSV 文件储存、列表操作、分页判断完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/anjuke其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】页面整体分析分析 安居客武汉二手房页面,这次爬取实战准备使用 BeautifulSoup 解析库,熟练 BeautifulSoup 解析库的用法,注意到该页面与其他页面不同的是,不能一次性看到到底有多少页,以前知道一共有多少页,直接一个循环爬取就行了,虽然可以通过改变 url 来尝试找到最后一页,但是这样就显得不程序员了😂,因此可以通过 BeautifulSoup 解析 下一页按钮,提取到下一页的 url,直到没有 下一页按钮 这个元素为止,从而实现所有页面的爬取,剩下的信息提取和储存就比较简单了 【2x00】解析模块分析页面,可以发现每条二手房信息都是包含在 <li> 标签内的,因此可以使用 BeautifulSoup 解析页面得到所有的 <li> 标签,然后再循环访问每个 <li> 标签,依次解析得到每条二手房的各种信息 1234567891011121314151617181920212223242526272829303132333435363738394041def parse_pages(url, num): response = requests.get(url=url, headers=headers) soup = BeautifulSoup(response.text, 'lxml') result_list = soup.find_all('li', class_='list-item') # print(len(result_list)) for result in result_list: # 标题 title = result.find('a', class_='houseListTitle').text.strip() # print(title) # 户型 layout = result.select('.details-item > span')[0].text # print(layout) # 面积 cover = result.select('.details-item > span')[1].text # print(cover) # 楼层 floor = result.select('.details-item > span')[2].text # print(floor) # 建造年份 year = result.select('.details-item > span')[3].text # print(year) # 单价 unit_price = result.find('span', class_='unit-price').text.strip() # print(unit_price) # 总价 total_price = result.find('span', class_='price-det').text.strip() # print(total_price) # 关键字 keyword = result.find('div', class_='tags-bottom').text.strip() # print(keyword) # 地址 address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\\n', '') # print(address) # 详情页url details_url = result.find('a', class_='houseListTitle')['href'] # print(details_url)if __name__ == '__main__': start_num = 0 start_url = 'https://wuhan.anjuke.com/sale/' parse_pages(start_url, start_num) 【3x00】循环爬取模块前面已经分析过,该网页是无法一下就能看到一共有多少页的,尝试找到最后一页,发现一共有50页,那么此时就可以搞个循环,一直到第50页就行了,但是如果有一天页面数增加了呢,那么代码的可维护性就不好了,我们可以观察 下一页按钮 ,当存在下一页的时候,是 <a> 标签,并且带有下一页的 URL,不存在下一页的时候是 <i> 标签,因此可以写个 if 语句,判断是否存在此 <a> 标签,若存在,表示有下一页,然后提取其 href 属性并传给解析模块,实现后面所有页面的信息提取,此外,由于安居客有反爬系统,我们还可以利用 Python中的 random.randint() 方法,在两个数值之间随机取一个数,传入 time.sleep() 方法,实现随机暂停爬取 12345678910# 判断是否还有下一页next_url = soup.find_all('a', class_='aNxt')if len(next_url) != 0: num += 1 print('第' + str(num) + '页数据爬取完毕!') # 3-60秒之间随机暂停 time.sleep(random.randint(3, 60)) parse_pages(next_url[0].attrs['href'], num)else: print('所有数据爬取完毕!') 【4x00】数据储存模块数据储存比较简单,将每个二手房信息组成一个列表,依次写入到 anjuke.csv 文件中即可 1234results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url]with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f: w = csv.writer(f) w.writerow(results) 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-10-09# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: anjuke.py# @Software: PyCharm# =============================================import requestsimport timeimport csvimport randomfrom bs4 import BeautifulSoupheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def parse_pages(url, num): response = requests.get(url=url, headers=headers) soup = BeautifulSoup(response.text, 'lxml') result_list = soup.find_all('li', class_='list-item') # print(len(result_list)) for result in result_list: # 标题 title = result.find('a', class_='houseListTitle').text.strip() # print(title) # 户型 layout = result.select('.details-item > span')[0].text # print(layout) # 面积 cover = result.select('.details-item > span')[1].text # print(cover) # 楼层 floor = result.select('.details-item > span')[2].text # print(floor) # 建造年份 year = result.select('.details-item > span')[3].text # print(year) # 单价 unit_price = result.find('span', class_='unit-price').text.strip() # print(unit_price) # 总价 total_price = result.find('span', class_='price-det').text.strip() # print(total_price) # 关键字 keyword = result.find('div', class_='tags-bottom').text.strip() # print(keyword) # 地址 address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\\n', '') # print(address) # 详情页url details_url = result.find('a', class_='houseListTitle')['href'] # print(details_url) results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url] with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f: w = csv.writer(f) w.writerow(results) # 判断是否还有下一页 next_url = soup.find_all('a', class_='aNxt') if len(next_url) != 0: num += 1 print('第' + str(num) + '页数据爬取完毕!') # 3-60秒之间随机暂停 time.sleep(random.randint(3, 60)) parse_pages(next_url[0].attrs['href'], num) else: print('所有数据爬取完毕!')if __name__ == '__main__': with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as fp: writer = csv.writer(fp) writer.writerow(['标题', '户型', '面积', '楼层', '建造年份', '单价', '总价', '关键字', '地址', '详情页地址']) start_num = 0 start_url = 'https://wuhan.anjuke.com/sale/' parse_pages(start_url, start_num) 【6x00】数据截图 【7x00】程序不足的地方 虽然使用了随机暂停爬取的方法,但是在爬取了大约 20 页的数据后依然会出现验证页面,导致程序终止 原来设想的是可以由用户手动输入城市的拼音来查询不同城市的信息,方法是把用户输入的城市拼音和其他参数一起构造成一个 URL,然后对该 URL 发送请求,判断请求返回的代码,如果是 200 就代表可以访问,也就是用户输入的城市是正确的,然而发现即便是输入错误,该 URL 依然可以访问,只不过会跳转到一个正确的页面,没有搞清楚是什么原理,也就无法实现由用户输入城市来查询这个功能","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"安居客","slug":"安居客","permalink":"https://www.itrhx.com/tags/安居客/"}]},{"title":"使用 Hexo-Git-Backup 插件备份你的 Hexo 博客","slug":"A53-hexo-backup","date":"2019-09-29T10:02:15.603Z","updated":"2019-12-29T07:19:49.434Z","comments":true,"path":"2019/09/29/A53-hexo-backup/","link":"","permalink":"https://www.itrhx.com/2019/09/29/A53-hexo-backup/","excerpt":"","text":"欢迎关注我的 CSDN 专栏:《个人博客搭建:Hexo+Github Pages》,从搭建到美化一条龙,帮你解决 Hexo 常见问题! 由于 Hexo 博客是静态托管的,所有的原始数据都保存在本地,如果哪一天电脑坏了,或者是误删了本地数据,那就是叫天天不应叫地地不灵了,此时定时备份就显得比较重要了,常见的备份方法有:打包数据保存到U盘、云盘或者其他地方,但是早就有大神开发了备份插件:hexo-git-backup ,只需要一个命令就可以将所有数据包括主题文件备份到 github 了 首先进入你博客目录,输入命令 hexo version 查看 Hexo 版本,如图所示,我的版本是 3.7.1: 安装备份插件,如果你的 Hexo 版本是 2.x.x,则使用以下命令安装: 1$ npm install hexo-git-backup@0.0.91 --save 如果你的 Hexo 版本是 3.x.x,则使用以下命令安装: 1$ npm install hexo-git-backup --save 到 Hexo 博客根目录的 _config.yml 配置文件里添加以下配置: 1234567backup: type: git theme: material-x-1.2.1 message: Back up my www.itrhx.com blog repository: github: git@github.com:TRHX/TRHX.github.io.git,backup coding: git@git.dev.tencent.com:TRHX/TRHX.git,backup 参数解释: theme:你要备份的主题名称 message:自定义提交信息 repository:仓库名,注意仓库地址后面要添加一个分支名,比如我就创建了一个 backup 分支 最后使用以下命令备份你的博客: 1$ hexo backup 或者使用以下简写命令也可以: 1$ hexo b 备份成功后可以在你的仓库分支下看到备份的原始文件:","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"备份","slug":"备份","permalink":"https://www.itrhx.com/tags/备份/"}]},{"title":"Python3 爬虫实战 — 豆瓣电影TOP250","slug":"A52-pyspider-doubantop250","date":"2019-09-28T08:35:19.823Z","updated":"2019-10-21T04:01:29.248Z","comments":true,"path":"2019/09/28/A52-pyspider-doubantop250/","link":"","permalink":"https://www.itrhx.com/2019/09/28/A52-pyspider-doubantop250/","excerpt":"爬取时间:2019-09-27爬取难度:★★☆☆☆☆请求链接:https://movie.douban.com/top250 以及每部电影详情页爬取目标:爬取榜单上每一部电影详情页的数据,保存为 CSV 文件;下载所有电影海报到本地涉及知识:请求库 requests、解析库 lxml、Xpath 语法、正则表达式、CSV 和二进制数据储存、列表操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/douban-top250其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-09-27爬取难度:★★☆☆☆☆请求链接:https://movie.douban.com/top250 以及每部电影详情页爬取目标:爬取榜单上每一部电影详情页的数据,保存为 CSV 文件;下载所有电影海报到本地涉及知识:请求库 requests、解析库 lxml、Xpath 语法、正则表达式、CSV 和二进制数据储存、列表操作完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/douban-top250其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察豆瓣电影 Top 250,请求地址为:https://movie.douban.com/top250 每页展示25条电影信息,照例翻页观察 url 的变化: 第一页:https://movie.douban.com/top250 第二页:https://movie.douban.com/top250?start=25&filter= 第三页:https://movie.douban.com/top250?start=50&filter= 一共有10页,每次改变的是 start 的值,利用一个 for 循环,从 0 到 250 每隔 25 取一个值拼接到 url,实现循环爬取每一页,由于我们的目标是进入每一部电影的详情页,然后爬取详情页的内容,所以我们可以使用 Xpath 提取每一页每部电影详情页的 URL,将其赋值给 m_urls,并返回 m_urls,m_urls 是一个列表,列表元素就是电影详情页的 URL 12345678910def index_pages(number): url = 'https://movie.douban.com/top250?start=%s&filter=' % number index_response = requests.get(url=url, headers=headers) tree = etree.HTML(index_response.text) m_urls = tree.xpath(\"//li/div/div/a/@href\") return m_urlsif __name__ == '__main__': for i in range(0, 250, 25): movie_urls = index_pages(i) 【2x00】解析模块定义一个解析函数 parse_pages(),利用 for 循环,依次提取 index_pages() 函数返回的列表中的元素,也就是每部电影详情页的 URL,将其传给解析函数进行解析 1234567891011def index_pages(number): expressionsdef parse_pages(url): expressionsif __name__ == '__main__': for i in range(0, 250, 25): movie_urls = index_pages(i) for movie_url in movie_urls: results = parse_pages(movie_url) 详细看一下解析函数 parse_pages(),首先要对接收到的详情页 URL 发送请求,获取响应内容,然后再使用 Xpath 提取相关信息 123def parse_pages(url): movie_pages = requests.get(url=url, headers=headers) parse_movie = etree.HTML(movie_pages.text) 【2x01】Xpath 解析排名、电影名、评分信息其中排名、电影名和评分信息是最容易匹配到的,直接使用 Xpath 语法就可以轻松解决: 12345678# 排名ranking = parse_movie.xpath(\"//span[@class='top250-no']/text()\")# 电影名name = parse_movie.xpath(\"//h1/span[1]/text()\")# 评分score = parse_movie.xpath(\"//div[@class='rating_self clearfix']/strong/text()\") 【2x02】Xpath 解析参评人数接下来准备爬取有多少人参与了评价,分析一下页面: 如果只爬取这个 <span> 标签下的数字的话,没有任何提示信息,别人看了不知道是啥东西,所以把 人评价 这三个字也爬下来的话就比较好了,但是可以看到数字和文字不在同一个元素标签下,而且文字部分还有空格,要爬取的话就要把 class="rating_people" 的 a 标签下所有的 text 提取出来,然后再去掉空格: 123456789# 参评人数# 匹配a节点value = parse_movie.xpath(\"//a[@class='rating_people']\")# 提取a节点下所有文本string = [value[0].xpath('string(.)')]# 去除多余空格number = [a.strip() for a in string]# 此时 number = ['1617307人评价'] 这样做太麻烦了,我们可以直接提取数字,得到一个列表,然后使用另一个带有提示信息的列表,将两个列表的元素合并,组成一个新列表,这个新列表的元素就是提示信息+人数123456# 参评人数value = parse_movie.xpath(\"//span[@property='v:votes']/text()\")# 合并元素number = [\" \".join(['参评人数:'] + value)]# 此时 number = ['参评人数:1617307'] 【2x03】正则表达式解析制片国家、语言接下来尝试爬取制片国家/地区、语言等信息: 分析页面可以观察到,制片国家/地区和语言结构比较特殊,没有特别的 class 或者 id 属性,所包含的层次关系也太复杂,所以这里为了简便,直接采用正则表达式来匹配信息,就没有那么复杂了: 1234567# 制片国家/地区value = re.findall('<span class=\"pl\">制片国家/地区:</span>(.*?)<br/>', movie_pages.text)country = [\" \".join(['制片国家:'] + value)]# 语言value = re.findall('<span class=\"pl\">语言:</span>(.*?)<br/>', movie_pages.text)language = [\" \".join(['语言:'] + value)] 【3x00】返回解析数据其他剩下的信息皆可利用以上方法进行提取,所有信息提取完毕,最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表 1return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url) 【4x00】数据储存模块定义一个数据保存函数 save_results() 1234def save_results(data): with open('douban.csv', 'a', encoding=\"utf-8-sig\") as fp: writer = csv.writer(fp) writer.writerow(data) 注意:编码方式要设置为 utf-8-sig,如果设置为 utf-8,则文件会乱码,不设置编码,则可能会报一下类似错误: 1UnicodeEncodeError: 'gbk' codec can't encode character '\\ub3c4' in position 9: illegal multibyte sequence 可以看到错误出现在 \\ub3c4 上,将该 Unicode 编码转换为中文为 도,发现正是排名第 19 的电影:熔炉 도가니,因为标题有韩文,所以在储存为 CSV 文件时会报编码错误,而将编码设置为 utf-8-sig 就不会报错,具体原因参见:《Python 中文日文汉字乱码处理utf-8-sig》 接下来是保存电影的海报到本地: 1234567891011# 保存电影海报poster = parse_movie.xpath(\"//div[@id='mainpic']/a/img/@src\")response = requests.get(poster[0])name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0])poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg'dir_name = 'douban_poster'if not os.path.exists(dir_name): os.mkdir(dir_name)poster_path = dir_name + '/' + poster_namewith open(poster_path, \"wb\")as f: f.write(response.content) 解析电影详情页,使用 Xpath 提取海报的 URL,向该 URL 发送请求 图片以 排名+电影名.jpg 的方式命名,但是由于提取的电影名部分含有特殊字符,比如排名第 10 的电影:忠犬八公的故事 Hachi: A Dog’s Tale,其中有个冒号,而 Windows 文件命名是不能包含这些字符的,所以我们直接去除电影名包含的英文字符、空白字符、特殊字符,只留下中文,代码实现: name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0]) 定义一个文件夹名称 douban_poster,利用 os 模块判断当前是否存在该文件夹,若不存在就创建一个 最后以二进制形式保存海报到当前目录的 douban_poster 文件夹下 【5x00】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-09-27# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: douban.py# @Software: PyCharm# =============================================import requestsfrom lxml import etreeimport csvimport reimport timeimport osheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def index_pages(number): url = 'https://movie.douban.com/top250?start=%s&filter=' % number index_response = requests.get(url=url, headers=headers) tree = etree.HTML(index_response.text) m_urls = tree.xpath(\"//li/div/div/a/@href\") return m_urlsdef parse_pages(url): movie_pages = requests.get(url=url, headers=headers) parse_movie = etree.HTML(movie_pages.text) # 排名 ranking = parse_movie.xpath(\"//span[@class='top250-no']/text()\") # 电影名 name = parse_movie.xpath(\"//h1/span[1]/text()\") # 评分 score = parse_movie.xpath(\"//div[@class='rating_self clearfix']/strong/text()\") # 参评人数 value = parse_movie.xpath(\"//span[@property='v:votes']/text()\") number = [\" \".join(['参评人数:'] + value)] # value = parse_movie.xpath(\"//a[@class='rating_people']\") # string = [value[0].xpath('string(.)')] # number = [a.strip() for a in string] # print(number) # 类型 value = parse_movie.xpath(\"//span[@property='v:genre']/text()\") types = [\" \".join(['类型:'] + value)] # 制片国家/地区 value = re.findall('<span class=\"pl\">制片国家/地区:</span>(.*?)<br/>', movie_pages.text) country = [\" \".join(['制片国家:'] + value)] # 语言 value = re.findall('<span class=\"pl\">语言:</span>(.*?)<br/>', movie_pages.text) language = [\" \".join(['语言:'] + value)] # 上映时期 value = parse_movie.xpath(\"//span[@property='v:initialReleaseDate']/text()\") date = [\" \".join(['上映日期:'] + value)] # 片长 value = parse_movie.xpath(\"//span[@property='v:runtime']/text()\") time = [\" \".join(['片长:'] + value)] # 又名 value = re.findall('<span class=\"pl\">又名:</span>(.*?)<br/>', movie_pages.text) other_name = [\" \".join(['又名:'] + value)] # 导演 value = parse_movie.xpath(\"//div[@id='info']/span[1]/span[@class='attrs']/a/text()\") director = [\" \".join(['导演:'] + value)] # 编剧 value = parse_movie.xpath(\"//div[@id='info']/span[2]/span[@class='attrs']/a/text()\") screenwriter = [\" \".join(['编剧:'] + value)] # 主演 value = parse_movie.xpath(\"//div[@id='info']/span[3]\") performer = [value[0].xpath('string(.)')] # URL m_url = ['豆瓣链接:' + movie_url] # IMDb链接 value = parse_movie.xpath(\"//div[@id='info']/a/@href\") imdb_url = [\" \".join(['IMDb链接:'] + value)] # 保存电影海报 poster = parse_movie.xpath(\"//div[@id='mainpic']/a/img/@src\") response = requests.get(poster[0]) name2 = re.sub(r'[A-Za-z\\:\\s]', '', name[0]) poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg' dir_name = 'douban_poster' if not os.path.exists(dir_name): os.mkdir(dir_name) poster_path = dir_name + '/' + poster_name with open(poster_path, \"wb\")as f: f.write(response.content) return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)def save_results(data): with open('douban.csv', 'a', encoding=\"utf-8-sig\") as fp: writer = csv.writer(fp) writer.writerow(data)if __name__ == '__main__': num = 0 for i in range(0, 250, 25): movie_urls = index_pages(i) for movie_url in movie_urls: results = parse_pages(movie_url) for result in results: num += 1 save_results(result) print('第' + str(num) + '条电影信息保存完毕!') time.sleep(3) 【6x00】数据截图 【7x00】程序不足的地方程序不足的地方:豆瓣电影有反爬机制,当程序爬取到大约 150 条数据的时候,IP 就会被封掉,第二天 IP 才会解封,可以考虑综合使用多个代理、多个 User-Agent、随机时间暂停等方法进行爬取","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"豆瓣电影","slug":"豆瓣电影","permalink":"https://www.itrhx.com/tags/豆瓣电影/"}]},{"title":"Python3 爬虫实战 — 猫眼电影TOP100","slug":"A51-pyspider-maoyantop100","date":"2019-09-24T11:31:56.965Z","updated":"2019-10-21T04:00:20.669Z","comments":true,"path":"2019/09/24/A51-pyspider-maoyantop100/","link":"","permalink":"https://www.itrhx.com/2019/09/24/A51-pyspider-maoyantop100/","excerpt":"爬取时间:2019-09-23爬取难度:★☆☆☆☆☆请求链接:https://maoyan.com/board/4爬取目标:猫眼 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址,数据保存为 CSV 文件涉及知识:请求库 requests、解析库 lxml、Xpath 语法、CSV 文件储存完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/maoyan-top100其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278","text":"爬取时间:2019-09-23爬取难度:★☆☆☆☆☆请求链接:https://maoyan.com/board/4爬取目标:猫眼 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址,数据保存为 CSV 文件涉及知识:请求库 requests、解析库 lxml、Xpath 语法、CSV 文件储存完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/maoyan-top100其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278 【1x00】循环爬取网页模块观察猫眼电影TOP100榜,请求地址为:https://maoyan.com/board/4 每页展示10条电影信息,翻页观察 url 变化: 第一页:https://maoyan.com/board/4 第二页:https://maoyan.com/board/4?offset=10 第三页:https://maoyan.com/board/4?offset=20 一共有10页,利用一个 for 循环,从 0 到 100 每隔 10 取一个值拼接到 url,实现循环爬取每一页 12345678def index_page(number): url = 'https://maoyan.com/board/4?offset=%s' % number response = requests.get(url=url, headers=headers) return response.textif __name__ == '__main__': for i in range(0, 100, 10): index = index_page(i) 【2x00】解析模块定义一个页面解析函数 parse_page(),使用 lxml 解析库的 Xpath 方法依次提取电影排名(ranking)、电影名称(movie_name)、主演(performer)、上映时间(releasetime)、评分(score)、电影封面图 url(movie_img) 通过对主演部分的提取发现有多余的空格符和换行符,循环 performer 列表,使用 strip() 方法去除字符串头尾空格和换行符 电影评分分为整数部分和小数部分,依次提取两部分,循环遍历组成一个完整的评分 最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表 123456789101112131415161718def parse_page(content): tree = etree.HTML(content) # 电影排名 ranking = tree.xpath(\"//dd/i/text()\") # 电影名称 movie_name = tree.xpath('//p[@class=\"name\"]/a/text()') # 主演 performer = tree.xpath(\"//p[@class='star']/text()\") performer = [p.strip() for p in performer] # 上映时间 releasetime = tree.xpath('//p[@class=\"releasetime\"]/text()') # 评分 score1 = tree.xpath('//p[@class=\"score\"]/i[@class=\"integer\"]/text()') score2 = tree.xpath('//p[@class=\"score\"]/i[@class=\"fraction\"]/text()') score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))] # 电影封面图 movie_img = tree.xpath('//img[@class=\"board-img\"]/@data-src') return zip(ranking, movie_name, performer, releasetime, score, movie_img) 【3x00】数据储存模块定义一个 save_results() 函数,将所有数据保存到 maoyan.csv 文件 1234def save_results(result): with open('maoyan.csv', 'a') as fp: writer = csv.writer(fp) writer.writerow(result) 【4x00】完整代码1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859# =============================================# --*-- coding: utf-8 --*--# @Time : 2019-09-23# @Author : TRHX# @Blog : www.itrhx.com# @CSDN : https://blog.csdn.net/qq_36759224# @FileName: maoyan.py# @Software: PyCharm# =============================================import requestsfrom lxml import etreeimport csvheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}def index_page(number): url = 'https://maoyan.com/board/4?offset=%s' % number response = requests.get(url=url, headers=headers) return response.textdef parse_page(content): tree = etree.HTML(content) # 电影排名 ranking = tree.xpath(\"//dd/i/text()\") # 电影名称 movie_name = tree.xpath('//p[@class=\"name\"]/a/text()') # 主演 performer = tree.xpath(\"//p[@class='star']/text()\") performer = [p.strip() for p in performer] # 上映时间 releasetime = tree.xpath('//p[@class=\"releasetime\"]/text()') # 评分 score1 = tree.xpath('//p[@class=\"score\"]/i[@class=\"integer\"]/text()') score2 = tree.xpath('//p[@class=\"score\"]/i[@class=\"fraction\"]/text()') score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))] # 电影封面图 movie_img = tree.xpath('//img[@class=\"board-img\"]/@data-src') return zip(ranking, movie_name, performer, releasetime, score, movie_img)def save_results(result): with open('maoyan.csv', 'a') as fp: writer = csv.writer(fp) writer.writerow(result)if __name__ == '__main__': print('开始爬取数据...') for i in range(0, 100, 10): index = index_page(i) results = parse_page(index) for i in results: save_results(i) print('数据爬取完毕!') 【4x00】数据截图","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫实战","slug":"Python3-学习笔记/爬虫实战","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫实战/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"猫眼电影","slug":"猫眼电影","permalink":"https://www.itrhx.com/tags/猫眼电影/"}]},{"title":"Python3 爬虫学习笔记 C18","slug":"A50-Python3-spider-C18","date":"2019-09-21T03:59:30.358Z","updated":"2019-09-24T12:41:19.337Z","comments":true,"path":"2019/09/21/A50-Python3-spider-C18/","link":"","permalink":"https://www.itrhx.com/2019/09/21/A50-Python3-spider-C18/","excerpt":"Python3 爬虫学习笔记第十八章 —— 【爬虫框架 pyspider — 深入理解】","text":"Python3 爬虫学习笔记第十八章 —— 【爬虫框架 pyspider — 深入理解】 【18.1】启动参数常用启动命令:pyspider all,完整命令结构为:pyspider [OPTIONS] COMMAND [ARGS],OPTIONS 为可选参数,包含以下参数: -c, –config FILENAME:指定配置文件名称 –logging-config TEXT:日志配置文件名称,默认: pyspider/pyspider/logging.conf –debug:开启调试模式 –queue-maxsize INTEGER:队列的最大长度 –taskdb TEXT:taskdb 的数据库连接字符串,默认: sqlite –projectdb TEXT:projectdb 的数据库连接字符串,默认: sqlite –resultdb TEXT:resultdb 的数据库连接字符串,默认: sqlite –message-queue TEXT:消息队列连接字符串,默认: multiprocessing.Queue –phantomjs-proxy TEXT:PhantomJS 使用的代理,ip:port 的形式 –data-path TEXT:数据库存放的路径 –add-sys-path / –not-add-sys-path:将当前工作目录添加到python lib搜索路径 –version:显示 pyspider 的版本信息 –help:显示帮助信息 配置文件为一个 JSON 文件,一般为 config.json 文件,常用配置如下: 123456789101112{ \"taskdb\": \"mysql+taskdb://username:password@host:port/taskdb\", \"projectdb\": \"mysql+projectdb://username:password@host:port/projectdb\", \"resultdb\": \"mysql+resultdb://username:password@host:port/resultdb\", \"message_queue\": \"amqp://username:password@host:port/%2F\", \"webui\": { \"port\": 5000, \"username\": \"some_name\", \"password\": \"some_passwd\", \"need-auth\": true }} 可以设置对应的用户名,密码,端口等信息,使用命令 pyspider -c config.json all 即可运行 【18.2】运行单个组件pyspider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分,都可以单独运行,基本命令: pyspider [component_name] [options] 【18.2.1】运行 Scheduler1pyspider scheduler [OPTIONS] 123456789101112Options: --xmlrpc /--no-xmlrpc --xmlrpc-host TEXT --xmlrpc-port INTEGER --inqueue-limit INTEGER 任务队列的最大长度,如果满了则新的任务会被忽略 --delete-time INTEGER 设置为 delete 标记之前的删除时间 --active-tasks INTEGER 当前活跃任务数量配置 --loop-limit INTEGER 单轮最多调度的任务数量 --fail-pause-num INTEGER 上次失败时自动暂停项目暂停次数,任务失败,将0设置为禁用 --scheduler-cls TEXT Scheduler 使用的类 --threads TEXT ThreadBaseScheduler 的线程号,默认值:4 --help 显示帮助信息 【18.2.2】运行 Fetcher1pyspider fetcher [OPTIONS] 123456789101112Options: --xmlrpc /--no-xmlrpc --xmlrpc-host TEXT --xmlrpc-port INTEGER --poolsize INTEGER 同时请求的个数 --proxy TEXT 使用的代理 --user-agent TEXT 使用的 User-Agent --timeout TEXT 超时时间 --phantomjs-endpoint TEXT phantomjs 的端点,通过 pyspider 启动 phantomjs --splash-endpoint TEXT 执行 splash 的端点:http://splash.readthedocs.io/en/stable/api.html execut --fetcher-cls TEXT Fetcher 使用的类 --help 显示帮助信息 【18.2.3】运行 Processer1pyspider processor [OPTIONS] 1234Options: --processor-cls TEXT Processor 使用的类 --process-time-limit INTEGER 脚本处理时间限制 --help 显示帮助信息 【18.2.4】运行 WebUI1pyspider webui [OPTIONS] 1234567891011121314Options: --host TEXT 运行地址 --port INTEGER 运行端口 --cdn TEXT JS 和 CSS 的 CDN 服务器 --scheduler-rpc TEXT Scheduler 的 xmlrpc 路径 --fetcher-rpc TEXT Fetcher 的 xmlrpc 路径 --max-rate FLOAT 每个项目最大的 rate 值 --max-burst FLOAT 每个项目最大的 burst 值 --username TEXT Auth 验证的用户名 --password TEXT Auth 验证的密码 --need-auth 是否需要验证 --webui-instance TEXT 运行时使用的 Flask 应用 --process-time-limit INTEGER 调试中的脚本处理时间限制 --help 显示帮助信息 【18.3】crawl() 方法各参数参数文档:http://docs.pyspider.org/en/latest/apis/self.crawl/ url:爬取目标 URL,可以定义为单个 URL 字符串,也可以定义成 URL 列表 callback:回调函数,指定了该 URL 对应的响应内容用哪个方法来解析,示例: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.index_page) 代码解释:指定 callback 为 index_page,代表爬取 http://www.itrhx.com/ 得到的响应会用 index_page() 方法来解析,而 index_page() 方法的第一个参数就是响应对象,如下所示: 12def index_page(self, response): pass age:任务的有效时间,如果某个任务在有效时间内且已经被执行,则它不会重复执行,有如下两种设置方法: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, age=10*24*60*60) 123@config(age=10 * 24 * 60 * 60)def callback(self): pass priority:爬取任务的优先级,其值默认是 0,priority 的数值越大,对应的请求会越优先被调度,如下所示,2.html 页面将会优先爬取: 123def index_page(self): self.crawl('http://www.itrhx.com/1.html', callback=self.index_page) self.crawl('http://www.itrhx.com/2.html', callback=self.detail_page, priority=1) exetime:设置定时任务,其值是时间戳,默认是 0,即代表立即执行,如下所示表示该任务会在 30 分钟之后执行: 123import timedef on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, exetime=time.time()+30*60) retries:定义重试次数,其值默认是 3 itag:设置判定网页是否发生变化的节点值,在爬取时会判定次当前节点是否和上次爬取到的节点相同。如果节点相同,则证明页面没有更新,就不会重复爬取,如下所示: 123def index_page(self, response): for item in response.doc('.item').items(): self.crawl(item.find('a').attr.url, callback=self.detail_page, itag=item.find('.update-time').text()) 代码解释:设置 update-time 这个节点的值为 itag,在下次爬取时就会首先检测这个值有没有发生变化,如果没有变化,则不再重复爬取,否则执行爬取 auto_recrawl:开启时,爬取任务在过期后会重新执行,循环时间即定义的 age 时间长度,如下所示: 12def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.callback, age=5*60*60, auto_recrawl=True) 代码解释:定义 age 有效期为 5 小时,设置了 auto_recrawl 为 True,这样任务就会每 5 小时执行一次 method:HTTP 请求方式,默认为 GET,如果想发起 POST 请求,可以将 method 设置为 POST params:定义 GET 请求参数,如下所示表示两个等价的爬取任务: 123def on_start(self): self.crawl('http://httpbin.org/get', callback=self.callback, params={'a': 123, 'b': 'c'}) self.crawl('http://httpbin.org/get?a=123&b=c', callback=self.callback) data:POST 表单数据,当请求方式为 POST 时,我们可以通过此参数传递表单数据,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', data={'a': 123, 'b': 'c'}) files:上传的文件,需要指定文件名,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', files={field: {filename: 'content'}}) user_agent:爬取使用的 User-Agent headers:爬取时使用的 Headers,即 Request Headers cookies:爬取时使用的 Cookies,为字典格式 connect_timeout:在初始化连接时的最长等待时间,默认为 20 秒 timeout:抓取网页时的最长等待时间,默认为 120 秒 allow_redirects:确定是否自动处理重定向,默认为 True validate_cert:确定是否验证证书,此选项对 HTTPS 请求有效,默认为 True proxy:爬取时使用的代理,支持用户名密码的配置,格式为 username:password@hostname:port,如下所示: 12def on_start(self): self.crawl('http://httpbin.org/get', callback=self.callback, proxy='127.0.0.1:9743') 也可以设置 craw_config 来实现全局配置,如下所示: 12class Handler(BaseHandler): crawl_config = {'proxy': '127.0.0.1:9743'} fetch_type:开启 PhantomJS 渲染,如果遇到 JavaScript 渲染的页面,指定此字段即可实现 PhantomJS 的对接,pyspider 将会使用 PhantomJS 进行网页的抓取,如下所示: 12def on_start(self): self.crawl('https://www.taobao.com', callback=self.index_page, fetch_type='js') js_script:页面加载完毕后执行的 JavaScript 脚本,如下所示,页面加载成功后将执行页面混动的 JavaScript 代码,页面会下拉到最底部: 1234567def on_start(self): self.crawl('http://www.example.org/', callback=self.callback, fetch_type='js', js_script=''' function() {window.scrollTo(0,document.body.scrollHeight); return 123; } ''') js_run_at:代表 JavaScript 脚本运行的位置,是在页面节点开头还是结尾,默认是结尾,即 document-end js_viewport_width/js_viewport_height:JavaScript 渲染页面时的窗口大小 load_images:在加载 JavaScript 页面时确定是否加载图片,默认为否 save:在不同的方法之间传递参数,如下所示: 123456def on_start(self): self.crawl('http://www.example.org/', callback=self.callback, save={'page': 1})def callback(self, response): return response.save['page'] cancel:取消任务,如果一个任务是 ACTIVE 状态的,则需要将 force_update 设置为 True force_update:即使任务处于 ACTIVE 状态,那也会强制更新状态 【18.4】任务区分pyspider 判断两个任务是否是重复的是使用的是该任务对应的 URL 的 MD5 值作为任务的唯一 ID,如果 ID 相同,那么两个任务就会判定为相同,其中一个就不会爬取了 某些情况下,请求的链接是同一个,但是 POST 的参数不同,这时可以重写 task_id() 方法,利用 URL 和 POST 的参数来生成 ID,改变这个 ID 的计算方式来实现不同任务的区分: 1234import jsonfrom pyspider.libs.utils import md5stringdef get_taskid(self, task): return md5string(task['url']+json.dumps(task['fetch'].get('data', ''))) 【18.5】全局配置pyspider 可以使用 crawl_config 来指定全局的配置,配置中的参数会和 crawl() 方法创建任务时的参数合并: 12345class Handler(BaseHandler): crawl_config = { 'headers': {'User-Agent': 'GoogleBot',} 'proxy': '127.0.0.1:9743' } 【18.6】定时爬取通过 every 属性来设置爬取的时间间隔,如下代码表示每天执行一次爬取: 1234@every(minutes=24 * 60)def on_start(self): for url in urllist: self.crawl(url, callback=self.index_page) 注意事项:如果设置了任务的有效时间(age 参数),因为在有效时间内爬取不会重复,所以要把有效时间设置得比重复时间更短,这样才可以实现定时爬取 错误举例:设定任务的过期时间为 5 天,而自动爬取的时间间隔为 1 天,当第二次尝试重新爬取的时候,pyspider 会监测到此任务尚未过期,便不会执行爬取: 1234567@every(minutes=24 * 60)def on_start(self): self.crawl('http://www.itrhx.com/', callback=self.index_page)@config(age=5 * 24 * 60 * 60)def index_page(self): pass","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"pyspider","slug":"pyspider","permalink":"https://www.itrhx.com/tags/pyspider/"}]},{"title":"Python3 爬虫学习笔记 C17","slug":"A49-Python3-spider-C17","date":"2019-09-18T06:18:23.904Z","updated":"2019-09-24T12:41:15.652Z","comments":true,"path":"2019/09/18/A49-Python3-spider-C17/","link":"","permalink":"https://www.itrhx.com/2019/09/18/A49-Python3-spider-C17/","excerpt":"Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】","text":"Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】 【17.1】初识 pyspiderpyspider 是由国人 Binux 编写的一个 Python 爬虫框架 GitHub:https://github.com/binux/pyspider 官方文档(英文):http://docs.pyspider.org/ 非官方文档(中文):http://book.crifan.com/books/python_spider_pyspider/website/ 非官方文档(中文):https://www.cntofu.com/book/156/index.md pyspider 特性: python 脚本控制,可以使用任何 html 解析包(内置 pyquery) WEB 界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出 支持 MySQL、MongoDB、Redis、SQLite、Elasticsearch、PostgreSQL 对接了 PhantomJS,支持抓取 JavaScript 的页面 组件可替换,支持单机和分布式部署,支持 Docker 部署 提供优先级控制、失败重试、定时抓取等功能 Windows 系统安装 pyspider: 使用命令 pip install pyspider 安装,若报 PyCurl 相关错误,可访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl 下载对应 wheel 文件并使用命令 pip install whl文件名 安装即可 如果要爬取 JavaScrip 渲染的页面,还要下载 PhantomJS,并将 PhantomJS 的路径配置到环境变量里,或者直接复制到 Python 安装目录的 Scripts 文件夹,需要用到数据库储存的话,同样要安装好相应的数据库 准备就绪后,使用 pyspider all 命令可启动 pyspider,浏览器打开:http://localhost:5000/ 可以看到 pyspider 的 WebUI 管理界面 【17.2】使用 pyspider 【17.2.1】主界面当成功创建了一个爬虫项目后,主界面如下所示: Recent Active Tasks:查看最近活动的任务,会跳转到一个页面有列表显示 Create:创建一个新的爬虫项目 group:定义项目的分组,以方便管理,若 group 设置为 delete,则该项目将会在24小时之后删除 project name:爬虫项目名称 status:项目状态,各状态如下: TODO:一个爬虫项目刚刚创建时的状态,此状态下可以编辑 Python 代码 STOP:中止项目的运行 CHECKING:当一个运行中的项目被编辑时项目状态会被自动设置成此状态并中止运行 DEBUG:会运行爬虫,顾名思义找 BUG,一般来说用于调试阶段 RUNNING:运行爬虫项目 PAUSED:项目暂停运行,默认没有这个状态,但是当你在运行过程中突然断网就会出现此状态 rate/burst:当前的爬取速率,rate 代表 1 秒发出多少个请求,burst 相当于流量控制中的令牌桶算法的令牌数,rate 和 burst 设置的越大,爬取速率越快,速率的设定需要考虑本机性能和爬取过快被封的问题 avg time:任务平均时间 process:5m、1h、1d 分别指的是最近 5 分、1 小时、1 天内的请求情况,all 代表所有的请求情况,请求由不同颜色表示,蓝色的代表等待被执行的请求,绿色的代表成功的请求,黄色的代表请求失败后等待重试的请求,红色的代表失败次数过多而被忽略的请求 actions:对爬虫项目的操作,各操作如下: Run:立即执行任务,需要 status 为 RUNNING 或者 DEBUG 状态;假如在配置的调度执行时间内已经执行过,再点 run 是无效的,需要删除 task.db 里的数据才行 Active Tasks:查看当前爬虫项目的活动任务 Results:查看项目运行结果 【17.2.2】项目界面创建一个爬虫项目,界面如下所示: 创建项目:点击 Create 即可新建一个爬虫项目 Project Name:爬虫项目名称 Start URL(s) :爬虫入口地址,选填,可在项目中更改 项目创建完成进入调试界面: 调试界面右边:编写代码的区域 调试界面左边:调试的区域,用于执行代码,显示输出信息等用途 run:单步调试爬虫程序,点击就可运行当前任务 < > 箭头:上一步、下一步,用于调试过程中切换到上一步骤或者下一步骤 save:保存当前代码,当代码变更后只有保存了再运行才能得到最新结果 enable css selector helper: CSS 选择器辅助程序 web:页面预览 html:可以查看页面源代码 follows:表示爬取请求,点击可查看所有的请求 在新建一个爬虫项目的时候,pyspider 已经自动生成了如下代码: 123456789101112131415161718192021222324252627#!/usr/bin/env python# -*- encoding: utf-8 -*-# Created on 2019-09-17 21:18:13# Project: 2from pyspider.libs.base_handler import *class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('__START_URL__', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('a[href^=\"http\"]').items(): self.crawl(each.attr.href, callback=self.detail_page) @config(priority=2) def detail_page(self, response): return { \"url\": response.url, \"title\": response.doc('title').text(), } class Handler():pyspider 爬虫的主类,可以在此处定义爬取、解析、存储的逻辑。整个爬虫的功能只需要一个 Handler 即可完成 crawl_config 属性:项目的所有爬取配置将会统一定义到这里,如定义 headers、设置代理等,配置之后全局生效 on_start() 方法:爬取入口,初始的爬取请求会在这里产生,该方法通过调用 crawl() 方法即可新建一个爬取请求,第一个参数是爬取的 URL,另一个参数 callback 指定了这个页面爬取成功后用哪个方法进行解析,默认指定为 index_page() 方法,即如果这个 URL 对应的页面爬取成功了,那 Response 将交给 index_page() 方法解析 index_page() 方法:接收 Response 参数,Response 对接了 pyquery。直接调用 doc() 方法传入相应的 CSS 选择器,就可以像 pyquery 一样解析此页面,代码中默认是 a[href^="http"],即解析页面的所有链接,然后将链接遍历,再次调用了 crawl() 方法生成了新的爬取请求,同时再指定了 callback 为 detail_page,表示这些页面爬取成功了就调用 detail_page() 方法解析。index_page() 实现了两个功能,一是将爬取的结果进行解析,二是生成新的爬取请求 detail_page() 方法:同样接收 Response 作为参数。detail_page() 抓取的就是详情页的信息,就不会生成新的请求,只对 Response 对象做解析,解析之后将结果以字典的形式返回。当然也可以进行后续处理,如将结果保存到数据库等操作 PS:pyspider 默认的 web 预览页面窗口较小,可以找到 pyspider 文件夹有个 debug.min.css 文件(如:E:\\Python\\Lib\\site-packages\\pyspider\\webui\\static\\debug.min.css),搜索 iframe,将原样式:iframe{border-width:0;width:100%} 改为 iframe{border-width:0;width:100%;height:400px !important} 即可,清除浏览器缓存后就会生效! 【17.3】使用 pyspider 爬取去哪儿网爬取地址:http://travel.qunar.com/travelbook/list.htm爬取目标:去哪儿网旅游攻略,发帖作者、标题、正文等 【17.3.1】爬取首页创建一个名为 qunar 的爬虫项目,Start URL 设置为 http://travel.qunar.com/travelbook/list.htm ,点击 run 出现一个爬取请求 左边调试区域出现以下代码: 12345678{ \"process\": { \"callback\": \"on_start\" }, \"project\": \"qunar\", \"taskid\": \"data:,on_start\", \"url\": \"data:,on_start\"} callback 为 on_start,表示此时执行了 on_start() 方法。在 on_start() 方法中,利用 crawl() 方法即可生成一个爬取请求,点击 index_page 链接后面的箭头会出现许多新的爬取请求,即首页所包含的所有链接 此时左边调试区域代码变为: 123456789101112{ \"fetch\": {}, \"process\": { \"callback\": \"index_page\" }, \"project\": \"qunar\", \"schedule\": { \"age\": 864000 }, \"taskid\": \"73a789f99528a2bdc3ab83a13902962a\", \"url\": \"http://travel.qunar.com/travelbook/list.htm\"} callback 变为了 index_page,表示此时执行了 index_page() 方法。传入 index_page() 方法的 response 参数为刚才生成的第一个爬取请求的 response 对象,然后调用 doc() 方法,传入提取所有 a 节点的 CSS 选择器,获取 a 节点的属性 href,实现了页面所有链接的提取,随后遍历所有链接,调用 crawl() 方法,把每个链接构造成新的爬取请求,可以看到 follows 新生成了 229 个爬取请求。点击 web 按钮可以直接预览当前页面,点击 html 按钮可以查看此页面源代码 【17.3.2】信息匹配代码 for each in response.doc('a[href^="http"]').items(): 实现了对整个页面链接的获取,我们需要提取网页的攻略的标题,内容等信息,那么直接替换 doc() 方法里的匹配语句即可,pyspider 提供了非常方便的 CSS 选择器,点击 enable css selector helper 按钮后,选择要匹配的信息并点击,再点击箭头 add to editor 即可得到匹配语句 完成了 CSS 选择器的替换,点击 save 保存,再次点击 run 重新执行 index_page() 方法,可以看到 follows 变为了 10 个,即抓取到了 10 篇攻略 【17.3.3】抓取下一页数据每一页只有 10 篇攻略,想要爬取所有页面的攻略,必须要得到下一页的数据,优化 index_page() 方法: 123456@config(age=10 * 24 * 60 * 60)def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page) next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) 匹配下一页按钮,获取下一页按钮的 URL 并赋值给 next,将该 URL 传给 crawl() 方法,指定回调函数为 index_page() 方法,这样会再次调用 index_page() 方法,提取下一页的攻略标题 【17.3.4】抓取JS渲染数据随便点击一个获取到的攻略,预览该页面,可以观察到头图一直在加载中,切换到 html 查看源代码页面,可以观察到没有 img 节点,那么此处就是后期经过 JavaScript 渲染后才出现的 针对 JavaScript 渲染页面,可以通过 PhantomJS 来实现,具体到 pyspider 中,只需要在 index_page() 的 crawl() 抓取方法中添加一个参数 fetch_type 即可: 123456@config(age=10 * 24 * 60 * 60)def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js') next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) 保存之后再次运行即可看到正常页面 【17.3.5】抓取所有数据改写 detail_page() 方法,同样通过 CSS 选择器提取 URL、标题、日期、作者、正文、图片等信息: 1234567891011@config(priority=2)def detail_page(self, response): return { 'url': response.url, 'title': response.doc('#booktitle').text(), 'date': response.doc('.when .data').text(), 'day': response.doc('.howlong .data').text(), 'who': response.doc('.who .data').text(), 'text': response.doc('#b_panel_schedule').text(), 'image': response.doc('.cover_img').attr.src } 【17.3.6】启动爬虫项目该爬虫项目完整代码如下: 12345678910111213141516171819202122232425262728293031323334#!/usr/bin/env python# -*- encoding: utf-8 -*-# Created on 2019-09-18 09:48:29# Project: qunarfrom pyspider.libs.base_handler import *class Handler(BaseHandler): crawl_config = { } @every(minutes=24 * 60) def on_start(self): self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page) @config(age=10 * 24 * 60 * 60) def index_page(self, response): for each in response.doc('li > .tit > a').items(): self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js') next = response.doc('.next').attr.href self.crawl(next, callback=self.index_page) @config(priority=2) def detail_page(self, response): return { 'url': response.url, 'title': response.doc('#booktitle').text(), 'date': response.doc('.when .data').text(), 'day': response.doc('.howlong .data').text(), 'who': response.doc('.who .data').text(), 'text': response.doc('#b_panel_schedule').text(), 'image': response.doc('.cover_img').attr.src } 保存代码后,回到主界面,将项目 status 修改为 RUNNING ,点击 actions 的 run 按钮即可启动爬虫 点击 Active Tasks,即可查看最近请求的详细状况: 点击 Results,即可查看所有的爬取结果: 另外,右上角还可以选择 JSON、CSV 格式","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"pyspider","slug":"pyspider","permalink":"https://www.itrhx.com/tags/pyspider/"}]},{"title":"Hexo 博客提交百度、谷歌搜索引擎收录","slug":"A48-submit-search-engine-inclusion","date":"2019-09-17T07:59:46.143Z","updated":"2019-12-29T07:20:01.329Z","comments":true,"path":"2019/09/17/A48-submit-search-engine-inclusion/","link":"","permalink":"https://www.itrhx.com/2019/09/17/A48-submit-search-engine-inclusion/","excerpt":"","text":"● 写在前面(必看)网站在没有提交搜索引擎收录之前,直接搜索你网站的内容是搜不到的,只有提交搜索引擎之后,搜索引擎才能收录你的站点,通过爬虫抓取你网站的东西,对于 hexo 博客来说,如果你是部署在 GitHub Pages,那么你是无法被百度收录的,因为 GitHub 禁止了百度爬虫,最常见的解决办法是双线部署到 Coding Pages 和 GitHub Pages,因为百度爬虫可以爬取到 Coding 上的内容,从而实现百度收录,如果你的 hexo 博客还没有实现双线部署,请参考:《Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS》,另外百度收录的所需的时间较长,大约半个月左右才会看到效果! ● 查看网站是否被收录首先我们可以输入 site:域名 来查看域名是否被搜索引擎收录,如下图所示,表示没有收录: ● 百度资源平台添加网站访问百度搜索资源平台官网,注册或者登陆百度账号,依次选择【用户中心】-【站点管理】,添加你的网站,在添加站点时会让你选择协议头(http 或者 https),如果选择 https,它会验证你的站点,大约能在一天之内完成,我的网站已经实现了全站 https,因此选择了 https 协议,但是不知道为什么始终验证失败,实在是无解,只能选择 http 协议了,如果你的站点也实现了全站 https,也可以尝试一下 之后会让你验证网站所有权,提供三种验证方式: 文件验证:下载给定的文件,将其放到本地主题目录 source 文件夹,然后部署上去完成验证 HTML 标签验证:一般是给一个 meta 标签,放到首页 <head> 与 </head> 标签之间即可完成验证 CNAME 验证:个人觉得这种方法最简单,去域名 DNS 添加一个 CNAME 记录即可完成验证 ● 提交百度搜索百度提供了自动提交和手动提交两种方式,其中自动提交又分为主动推送、自动推送和 sitemap 三种方式,以下是官方给出的解释: 主动推送:最为快速的提交方式,推荐您将站点当天新产出链接立即通过此方式推送给百度,以保证新链接可以及时被百度收录 自动推送:是轻量级链接提交组件,将自动推送的 JS 代码放置在站点每一个页面源代码中,当页面被访问时,页面链接会自动推送给百度,有利于新页面更快被百度发现 sitemap:您可以定期将网站链接放到sitemap中,然后将sitemap提交给百度。百度会周期性的抓取检查您提交的sitemap,对其中的链接进行处理,但收录速度慢于主动推送 手动提交:如果您不想通过程序提交,那么可以采用此种方式,手动将链接提交给百度 四种提交方式对比: 方式 主动推送 自动推送 Sitemap 手动提交 速度 最快 —— —— —— 开发成本 高 低 中 不需开发 可提交量 低 高 高 低 是否建议提交历史连接 否 是 是 是 和其他提交方法是否有冲突 无 无 无 无 个人推荐同时使用主动推送和 sitemap 方式,下面将逐一介绍这四种提交方式的具体实现方法 ● 主动推送在博客根目录安装插件 npm install hexo-baidu-url-submit --save,然后在根目录 _config.yml 文件里写入以下配置: 12345baidu_url_submit: count: 1 # 提交最新的多少个链接 host: www.itrhx.com # 在百度站长平台中添加的域名 token: your_token # 秘钥 path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里 其中的 token 可以在【链接提交】-【自动提交】-【主动推送】下面看到,接口调用地址最后面 token=xxxxx 即为你的 token 同样是在根目录的 _config.yml 文件,大约第 17 行处,url 要改为在百度站长平台添加的域名,也就是你网站的首页地址: 1234# URLurl: https://www.itrhx.comroot: /permalink: :year/:month/:day/:title/ 最后,加入新的 deployer: 123456789# Deployment## Docs: https://hexo.io/docs/deployment.htmldeploy:- type: git repository: github: git@github.com:TRHX/TRHX.github.io.git # 这是原来的 github 配置 coding: git@git.dev.tencent.com:TRHX/TRHX.git # 这是原来的 coding 配置 branch: master- type: baidu_url_submitter # 这是新加的主动推送 最后执行 hexo g -d 部署一遍即可实现主动推送,推送成功的标志是:在执行部署命令最后会显示类似如下代码: 12{\"remain\":4999953,\"success\":47}INFO Deploy done: baidu_url_submitter 这表示有 47 个页面已经主动推送成功,remain 的意思是当天剩余的可推送 url 条数 主动推送相关原理介绍: 新链接的产生:hexo generate 会产生一个文本文件,里面包含最新的链接 新链接的提交:hexo deploy 会从上述文件中读取链接,提交至百度搜索引擎 该插件的 GitHub 地址:https://github.com/huiwang/hexo-baidu-url-submit ● 自动推送关于自动推送百度官网给出的解释是:自动推送是百度搜索资源平台为提高站点新增网页发现速度推出的工具,安装自动推送JS代码的网页,在页面被访问时,页面URL将立即被推送给百度 此时要注意,有些 hexo 主题集成了这项功能,比如 next 主题,在 themes\\next\\layout_scripts\\ 下有个 baidu_push.swig 文件,我们只需要把如下代码粘贴到该文件,然后在主题配置文件设置 baidu_push: true 即可 12345678910111213141516{% if theme.baidu_push %}<script>(function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName(\"script\")[0]; s.parentNode.insertBefore(bp, s);})();</script>{% endif %} 然而大部分主题是没有集成这项功能的,对于大部分主题来说,我们可以把以下代码粘贴到 head.ejs 文件的 <head> 与 </head> 标签之间即可,从而实现自动推送(比如我使用的是 Material X 主题,那么只需要把代码粘贴到 \\themes\\material-x\\layout\\_partial\\head.ejs 中即可) 1234567891011121314<script>(function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName(\"script\")[0]; s.parentNode.insertBefore(bp, s);})();</script> ● sitemap首先我们要使用以下命令生成一个网站地图: 12npm install hexo-generator-sitemap --save npm install hexo-generator-baidu-sitemap --save 这里也注意一下,将根目录的 _config.yml 文件,大约第 17 行处,url 改为在百度站长平台添加的域名,也就是你网站的首页地址: 1234# URLurl: https://www.itrhx.comroot: /permalink: :year/:month/:day/:title/ 然后使用命令 hexo g -d 将网站部署上去,然后访问 你的首页/sitemap.xml 或者 你的首页/baidusitemap.xml 就可以看到网站地图了 比如我的是:https://www.itrhx.com/baidusitemap.xml 或者 https://www.itrhx.com/sitemap.xml 其中 sitemap.xml 文件是搜索引擎通用的 sitemap 文件,baidusitemap.xml 是百度专用的 sitemap 文件 然后来到百度站长平台的 sitemap 提交页面,将你的 sitemap 地址提交即可,如果成功的话状态会显示为正常,初次提交要等几分钟,sitemap.xml 相比 baidusitemap.xml 来说等待时间也会更长,如果以后你博客有新的文章或其他页面,可以点击手动更新文件,更新一下新的 sitemap ● 手动提交手动提交不需要其他额外操作,直接把需要收录的页面的 url 提交即可,这种方法效率较低,更新较慢,不推荐使用 ● 提交谷歌搜索提交谷歌搜索引擎比较简单,在提交之前,我们依然可以使用 site:域名 查看网站是否被收录,我的网站搭建了有差不多一年了,之前也没提交过收录,不过谷歌爬虫的确是强大,即使没有提交过,现在也能看到有一百多条结果了: 接下来我们将网站提交谷歌搜索引擎搜索,进入谷歌站长平台,登录你的谷歌账号之后会让你验证网站所有权: 有两种验证方式,分别是网域和网址前缀,两种资源类型区别如下: 网址前缀资源 网域资源 说明 仅包含具有指定前缀(包括协议 http/https)的网址。如果希望资源匹配任何协议或子网域(http/https/www./m. 等),建议改为添加网域资源。 包括所有子网域(m、www 等)和多种协议(http、https、ftp)的网域级资源。 验证 多种类型 仅 DNS 记录验证 示例 资源 http://example.com/✔ http://example.com/dresses/1234X https://example.com/dresses/1234X http://www.example.com/dresses/1234 资源 example.com✔ http://example.com/dresses/1234✔ https://example.com/dresses/1234✔ http://www.example.com/dresses/1234✔ http://support.m.example.com/dresses/1234 由对比可知选择网域资源验证方式比较好,只需要一个域名就可以匹配到多种格式的 URL,之后会给你一个 TXT 的记录值,复制它到你域名 DNS 增加一个 TXT 记录,点击验证即可 提交谷歌收录比较简单,选择站点地图,将我们之前生成的 sitemap 提交就行了,过几分钟刷新一下看到成功字样表示提交成功!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"SEO","slug":"SEO","permalink":"https://www.itrhx.com/tags/SEO/"}]},{"title":"Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS","slug":"A47-hexo-deployed-to-github-and-coding","date":"2019-09-16T06:11:40.959Z","updated":"2020-03-14T08:00:06.127Z","comments":true,"path":"2019/09/16/A47-hexo-deployed-to-github-and-coding/","link":"","permalink":"https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/","excerpt":"","text":"部署到 Coding Pages 的好处:国内访问速度更快,可以提交百度收录(GitHub 禁止了百度的爬取) 部署到 Coding Pages 的坏处:就今年来说,Coding 不太稳定,随时有宕机的可能,群里的朋友已经经历过几次了,不过相信以后会越来越稳定的 部署过程中常见的问题:无法实现全站 HTTPS,Coding 申请 SSL 证书失败,浏览器可能会提示不是安全链接 本文前提:你已经将 Hexo 成功部署到了 GitHub Pages,如果还没有,请参考:《使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】》 本文将全面讲述如何成功双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS,同时解决一些常见的问题! 1.创建项目进入 Coding 官网,点击个人版登陆,没有账号就注册一个并登录,由于 Coding 已经被腾讯收购了,所以登录就会来到腾讯云开发者平台,点击创建项目 项目名称建议和你的用户名一致,这样做的好处是:到时候可以直接通过 user_name.coding.me 访问你的博客,如果项目名与用户名不一致,则需要通过 user_name.coding.me/project_name 才能访问,项目描述可以随便写 2.配置公钥配置 SSH 公钥方法与 GitHub Pages 的方式差不多,点击你的头像,依次选择【个人设置】-【SSH公钥】-【新增公钥】 前面部署到 GitHub Pages 的时候就已经有了一对公钥,我们直接将该公钥粘贴进去就行,公钥名称可以随便写,选中永久有效选项 PS:公钥储存位置一般在 C:\\Users\\用户名\\.ssh 目录下的 id_rsa.pub 文件里,用记事本打开复制其内容即可 添加公钥后,我们可以右键 Get Bash,输入以下命令来检查是否配置成功: 1ssh -T git@git.coding.net 若出现以下提示,则证明配置成功: 12Coding 提示: Hello XXX, You've connected to Coding.net via SSH. This is a personal key.XXX,你好,你已经通过 SSH 协议认证 Coding.net 服务,这是一个个人公钥 3.配置 _config.yml进入你的项目,在右下角有选择连接方式,选择 SSH 方式(HTTPS 方式也可以,但是这种方式有时候可能连接不上,SSH 连接不容易出问题),一键复制,然后打开你本地博客根目录的 _config.yml 文件,找到 deploy 关键字,添加 coding 地址:coding: git@git.dev.tencent.com:user_name/user_name.git,也就是刚刚复制的 SSH 地址 添加完成后先执行命令 hexo clean 清理一下缓存,然后执行命令 hexo g -d 将博客双线部署到 Coding Pages 和 GitHub Pages,如下图所示表示部署成功: 4.开启 Coding Pages进入你的项目,在代码栏下选择 Pages 服务,一键开启 Coding Pages,等待几秒后刷新网页即可看到已经开启的 Coding Pages,到目前为止,你就可以通过 xxxx.coding.me(比如我的是 trhx.coding.me)访问你的 Coding Pages 页面了 5.绑定域名并开启 HPPTS首先在你的域名 DNS 设置中添加一条 CNAME 记录指向 xxxx.coding.me,解析路线选择 默认,将 GitHub 的解析路线改为 境外,这样境外访问就会走 GitHub,境内就会走 Coding,也有人说阿里云是智能解析,自动分配路线,如果解析路线都是默认,境外访问同样会智能选择走 GitHub,境内走 Coding,我没有验证过,有兴趣的可以自己试试,我的解析如下图所示: 然后点击静态 Pages 应用右上角的设置,进入设置页面,这里要注意,如果你之前已经部署到了 GitHub Pages 并开启了 HTTPS,那么直接在设置页面绑定你自己的域名,SSL/TLS 安全证书就会显示申请错误,如下图所示,没有申请到 SSL 证书,当你访问你的网站时,浏览器就会提示不是安全连接 申请错误原因是:在验证域名所有权时会定位到 Github Pages 的主机上导致 SSL 证书申请失败 正确的做法是:先去域名 DNS 把 GitHub 的解析暂停掉,然后再重新申请 SSL 证书,大约十秒左右就能申请成功,然后开启强制 HTTPS 访问 这里也建议同时绑定有 www 前缀和没有 www 前缀的,如果要绑定没有 www 前缀的,首先要去域名 DNS 添加一个 A 记录,主机记录为 @,记录值为你博客 IP 地址,IP 地址可以在 cmd 命令行 ping 一下得到,然后在 Coding Pages 中设置其中一个为【首选】,另一个设置【跳转至首选】,这样不管用户是否输入 www 前缀都会跳到有 www 前缀的了 在博客资源引用的时候也要注意所有资源的 URL 必须是以 https:// 开头,不然浏览器依旧会提示不安全! 至此,我们的 Hexo 博客就成功双线部署到 Coding Pages 和 GitHub Pages 了,并且也实现了全站 HPPTS,最后来一张 GitHub Pages 和 Coding Pages 在国内的速度对比图,可以明显看到速度的提升","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"Coding Pages","slug":"Coding-Pages","permalink":"https://www.itrhx.com/tags/Coding-Pages/"},{"name":"GitHub Pages","slug":"GitHub-Pages","permalink":"https://www.itrhx.com/tags/GitHub-Pages/"}]},{"title":"Python3 爬虫学习笔记 C16","slug":"A46-Python3-spider-C16","date":"2019-09-13T16:44:50.577Z","updated":"2019-09-24T12:43:19.863Z","comments":true,"path":"2019/09/14/A46-Python3-spider-C16/","link":"","permalink":"https://www.itrhx.com/2019/09/14/A46-Python3-spider-C16/","excerpt":"Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】","text":"Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】 【16.1】关于 RedisRedis 是一个基于内存的高效的键值型(key-value)非关系型数据库,它支持存储的 value 类型非常多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合) 和 hash(哈希类型),它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,此外,它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。 【16.2】使用 Redis首先安装 Redis 和 redis-py 库,管理 Redis 可以使用可视化工具 Redis Desktop Manager,该工具现在收费了,分享个 0.8.8.384 的免费版本 安装 redis-py 库:pip install redisRedis 官网:https://redis.io官方文档:https://redis.io/documentation中文官网:http://www.redis.cn中文教程:http://www.runoob.com/redis/redis-tutorial.htmlGitHub:https://github.com/antirez/redisRedis Windows下载地址一:https://github.com/microsoftarchive/redis/releases (最新版 3.2.100,似乎不再更新)Redis Windows下载地址二:https://github.com/tporadowski/redis/releases (最新版)Redis Desktop Manager 官网:https://redisdesktop.com/Redis Desktop Manager 0.8.8.384 免费版:https://pan.baidu.com/s/18MKeCqT0MG0hc89jfkpIkA (提取码:3ovc) 利用 Python 连接 Redis 示例: 12345from redis import StrictRedisredis = StrictRedis(host='localhost', port=6379, db=0, password='000000')redis.set('name', 'TRHX')print(redis.get('name')) 传入 Redis 的地址、运行端口、使用的数据库和密码, 4 个参数默认值分别为 localhost、6379、0 和 None,声明一个 StrictRedis 对象,调用 set() 方法,设置一个键值对,输出结果如下: 1b'TRHX' 另外也可以使用 ConnectionPool 来连接: 1234from redis import StrictRedis, ConnectionPool pool = ConnectionPool(host='localhost', port=6379, db=0, password='000000') redis = StrictRedis(connection_pool=pool) ConnectionPool 也支持通过 URL 来构建: 123redis://[:password]@host:port/db # 创建 Redis TCP 连接rediss://[:password]@host:port/db # 创建 Redis TCP+SSL 连接unix://[:password]@/path/to/socket.sock?db=db # 创建 Redis UNIX socket 连接 代码示例: 12345from redis import StrictRedis, ConnectionPoolurl = 'redis://:000000@localhost:6379/0' pool = ConnectionPool.from_url(url) redis = StrictRedis(connection_pool=pool) 以下是有关的键操作、字符串操作、列表操作、集合操作、散列操作的各种方法,记录一下,方便查阅来源:《Python3 网络爬虫开发实战(崔庆才著)》Redis 命令参考:http://redisdoc.com/ 、http://doc.redisfans.com/ 【16.3】Key(键)操作 方法 作用 参数说明 示例 示例说明 示例结果 exists(name) 判断一个键是否存在 name:键名 redis.exists(‘name’) 是否存在 name 这个键 True delete(name) 删除一个键 name:键名 redis.delete(‘name’) 删除 name 这个键 1 type(name) 判断键类型 name:键名 redis.type(‘name’) 判断 name 这个键类型 b’string’ keys(pattern) 获取所有符合规则的键 pattern:匹配规则 redis.keys(‘n*’) 获取所有以 n 开头的键 [b’name’] randomkey() 获取随机的一个键 randomkey() 获取随机的一个键 b’name’ rename(src, dst) 重命名键 src:原键名;dst:新键名 redis.rename(‘name’, ‘nickname’) 将 name 重命名为 nickname True dbsize() 获取当前数据库中键的数目 dbsize() 获取当前数据库中键的数目 100 expire(name, time) 设定键的过期时间,单位为秒 name:键名;time:秒数 redis.expire(‘name’, 2) 将 name 键的过期时间设置为 2 秒 True ttl(name) 获取键的过期时间,单位为秒,-1 表示永久不过期 name:键名 redis.ttl(‘name’) 获取 name 这个键的过期时间 -1 move(name, db) 将键移动到其他数据库 name:键名;db:数据库代号 move(‘name’, 2) 将 name 移动到 2 号数据库 True flushdb() 删除当前选择数据库中的所有键 flushdb() 删除当前选择数据库中的所有键 True flushall() 删除所有数据库中的所有键 flushall() 删除所有数据库中的所有键 True 【16.4】String(字符串)操作 方法 作用 参数说明 示例 示例说明 示例结果 set(name, value) 给数据库中键名为 name 的 string 赋予值 value name:键名;value:值 redis.set(‘name’, ‘Bob’) 给 name 这个键的 value 赋值为 Bob True get(name) 返回数据库中键名为 name 的 string 的 value name:键名 redis.get(‘name’) 返回 name 这个键的 value b’Bob’ getset(name, value) 给数据库中键名为 name 的 string 赋予值 value 并返回上次的 value name:键名;value:新值 redis.getset(‘name’, ‘Mike’) 赋值 name 为 Mike 并得到上次的 value b’Bob’ mget(keys, *args) 返回多个键对应的 value 组成的列表 keys:键名序列 redis.mget([‘name’, ‘nickname’]) 返回 name 和 nickname 的 value [b’Mike’, b’Miker’] setnx(name, value) 如果不存在这个键值对,则更新 value,否则不变 name:键名 redis.setnx(‘newname’, ‘James’) 如果 newname 这个键不存在,则设置值为 James 第一次运行结果是 True,第二次运行结果是 False setex(name, time, value) 设置可以对应的值为 string 类型的 value,并指定此键值对应的有效期 name:键名;time:有效期;value:值 redis.setex(‘name’, 1, ‘James’) 将 name 这个键的值设为 James,有效期为 1 秒 True setrange(name, offset, value) 设置指定键的 value 值的子字符串 name:键名;offset:偏移量;value:值 redis.set(‘name’, ‘Hello’) redis.setrange (‘name’, 6, ‘World’) 设置 name 为 Hello 字符串,并在 index 为 6 的位置补 World 11,修改后的字符串长度 mset(mapping) 批量赋值 mapping:字典或关键字参数 redis.mset({‘name1’: ‘Durant’, ‘name2’: ‘James’}) 将 name1 设为 Durant,name2 设为 James True msetnx(mapping) 键均不存在时才批量赋值 mapping:字典或关键字参数 redis.msetnx({‘name3’: ‘Smith’, ‘name4’: ‘Curry’}) 在 name3 和 name4 均不存在的情况下才设置二者值 True incr(name, amount=1) 键名为 name 的 value 增值操作,默认为 1,键不存在则被创建并设为 amount name:键名;amount:增长的值 redis.incr(‘age’, 1) age 对应的值增 1,若不存在,则会创建并设置为 1 1,即修改后的值 decr(name, amount=1) 键名为 name 的 value 减值操作,默认为 1,键不存在则被创建并将 value 设置为 - amount name:键名;amount:减少的值 redis.decr(‘age’, 1) age 对应的值减 1,若不存在,则会创建并设置为-1 -1,即修改后的值 append(key, value) 键名为 key 的 string 的值附加 value key:键名 redis.append(‘nickname’, ‘OK’) 向键名为 nickname 的值后追加 OK 13,即修改后的字符串长度 substr(name, start, end=-1) 返回键名为 name 的 string 的子字符串 name:键名;start:起始索引;end:终止索引,默认为-1,表示截取到末尾 redis.substr(‘name’, 1, 4) 返回键名为 name 的值的字符串,截取索引为 1~4 的字符 b’ello’ getrange(key, start, end) 获取键的 value 值从 start 到 end 的子字符串 key:键名;start:起始索引;end:终止索引 redis.getrange(‘name’, 1, 4) 返回键名为 name 的值的字符串,截取索引为 1~4 的字符 b’ello 【16.5】Hash(哈希表)操作 方法 作用 参数说明 示例 示例说明 示例结果 hset(name, key, value) 向键名为 name 的散列表中添加映射 name:键名;key:映射键名;value:映射键值 hset(‘price’, ‘cake’, 5) 向键名为 price 的散列表中添加映射关系,cake 的值为 5 1,即添加的映射个数 hsetnx(name, key, value) 如果映射键名不存在,则向键名为 name 的散列表中添加映射 name:键名;key:映射键名;value:映射键值 hsetnx(‘price’, ‘book’, 6) 向键名为 price 的散列表中添加映射关系,book 的值为 6 1,即添加的映射个数 hget(name, key) 返回键名为 name 的散列表中 key 对应的值 name:键名;key:映射键名 redis.hget(‘price’, ‘cake’) 获取键名为 price 的散列表中键名为 cake 的值 5 hmget(name, keys, *args) 返回键名为 name 的散列表中各个键对应的值 name:键名;keys:键名序列 redis.hmget(‘price’, [‘apple’, ‘orange’]) 获取键名为 price 的散列表中 apple 和 orange 的值 [b’3’, b’7’] hmset(name, mapping) 向键名为 name 的散列表中批量添加映射 name:键名;mapping:映射字典 redis.hmset(‘price’, {‘banana’: 2, ‘pear’: 6}) 向键名为 price 的散列表中批量添加映射 True hincrby(name, key, amount=1) 将键名为 name 的散列表中映射的值增加 amount name:键名;key:映射键名;amount:增长量 redis.hincrby(‘price’, ‘apple’, 3) key 为 price 的散列表中 apple 的值增加 3 6,修改后的值 hexists(name, key) 键名为 name 的散列表中是否存在键名为键的映射 name:键名;key:映射键名 redis.hexists(‘price’, ‘banana’) 键名为 price 的散列表中 banana 的值是否存在 True hdel(name, *keys) 在键名为 name 的散列表中,删除键名为键的映射 name:键名;keys:键名序列 redis.hdel(‘price’, ‘banana’) 从键名为 price 的散列表中删除键名为 banana 的映射 True hlen(name) 从键名为 name 的散列表中获取映射个数 name:键名 redis.hlen(‘price’) 从键名为 price 的散列表中获取映射个数 6 hkeys(name) 从键名为 name 的散列表中获取所有映射键名 name:键名 redis.hkeys(‘price’) 从键名为 price 的散列表中获取所有映射键名 [b’cake’, b’book’, b’banana’, b’pear’] hvals(name) 从键名为 name 的散列表中获取所有映射键值 name:键名 redis.hvals(‘price’) 从键名为 price 的散列表中获取所有映射键值 [b’5’, b’6’, b’2’, b’6’] hgetall(name) 从键名为 name 的散列表中获取所有映射键值对 name:键名 redis.hgetall(‘price’) 从键名为 price 的散列表中获取所有映射键值对 {b’cake’: b’5’, b’book’: b’6’, b’orange’: b’7’, b’pear’: b’6’} 【16.6】List(列表)操作 方法 作用 参数说明 示例 示例说明 示例结果 rpush(name, *values) 在键名为 name 的列表末尾添加值为 value 的元素,可以传多个 name:键名;values:值 redis.rpush(‘list’, 1, 2, 3) 向键名为 list 的列表尾添加 1、2、3 3,列表大小 lpush(name, *values) 在键名为 name 的列表头添加值为 value 的元素,可以传多个 name:键名;values:值 redis.lpush(‘list’, 0) 向键名为 list 的列表头部添加 0 4,列表大小 llen(name) 返回键名为 name 的列表的长度 name:键名 redis.llen(‘list’) 返回键名为 list 的列表的长度 4 lrange(name, start, end) 返回键名为 name 的列表中 start 至 end 之间的元素 name:键名;start:起始索引;end:终止索引 redis.lrange(‘list’, 1, 3) 返回起始索引为 1 终止索引为 3 的索引范围对应的列表 [b’3’, b’2’, b’1’] ltrim(name, start, end) 截取键名为 name 的列表,保留索引为 start 到 end 的内容 name:键名;start:起始索引;end:终止索引 ltrim(‘list’, 1, 3) 保留键名为 list 的索引为 1 到 3 的元素 True lindex(name, index) 返回键名为 name 的列表中 index 位置的元素 name:键名;index:索引 redis.lindex(‘list’, 1) 返回键名为 list 的列表索引为 1 的元素 b’2’ lset(name, index, value) 给键名为 name 的列表中 index 位置的元素赋值,越界则报错 name:键名;index:索引位置;value:值 redis.lset(‘list’, 1, 5) 将键名为 list 的列表中索引为 1 的位置赋值为 5 True lrem(name, count, value) 删除 count 个键的列表中值为 value 的元素 name:键名;count:删除个数;value:值 redis.lrem(‘list’, 2, 3) 将键名为 list 的列表删除两个 3 1,即删除的个数 lpop(name) 返回并删除键名为 name 的列表中的首元素 name:键名 redis.lpop(‘list’) 返回并删除名为 list 的列表中的第一个元素 b’5’ rpop(name) 返回并删除键名为 name 的列表中的尾元素 name:键名 redis.rpop(‘list’) 返回并删除名为 list 的列表中的最后一个元素 b’2’ blpop(keys, timeout=0) 返回并删除名称在 keys 中的 list 中的首个元素,如果列表为空,则会一直阻塞等待 keys:键名序列;timeout:超时等待时间,0 为一直等待 redis.blpop(‘list’) 返回并删除键名为 list 的列表中的第一个元素 [b’5’] brpop(keys, timeout=0) 返回并删除键名为 name 的列表中的尾元素,如果 list 为空,则会一直阻塞等待 keys:键名序列;timeout:超时等待时间,0 为一直等待 redis.brpop(‘list’) 返回并删除名为 list 的列表中的最后一个元素 [b’2’] rpoplpush(src, dst) 返回并删除名称为 src 的列表的尾元素,并将该元素添加到名称为 dst 的列表头部 src:源列表的键;dst:目标列表的 key redis.rpoplpush(‘list’, ‘list2’) 将键名为 list 的列表尾元素删除并将其添加到键名为 list2 的列表头部,然后返回 b’2’ 【16.7】Set(集合)操作 方法 作用 参数说明 示例 示例说明 示例结果 sadd(name, *values) 向键名为 name 的集合中添加元素 name:键名;values:值,可为多个 redis.sadd(‘tags’, ‘Book’, ‘Tea’, ‘Coffee’) 向键名为 tags 的集合中添加 Book、Tea 和 Coffee 这 3 个内容 3,即插入的数据个数 srem(name, *values) 从键名为 name 的集合中删除元素 name:键名;values:值,可为多个 redis.srem(‘tags’, ‘Book’) 从键名为 tags 的集合中删除 Book 1,即删除的数据个数 spop(name) 随机返回并删除键名为 name 的集合中的一个元素 name:键名 redis.spop(‘tags’) 从键名为 tags 的集合中随机删除并返回该元素 b’Tea’ smove(src, dst, value) 从 src 对应的集合中移除元素并将其添加到 dst 对应的集合中 src:源集合;dst:目标集合;value:元素值 redis.smove(‘tags’, ‘tags2’, ‘Coffee’) 从键名为 tags 的集合中删除元素 Coffee 并将其添加到键为 tags2 的集合 True scard(name) 返回键名为 name 的集合的元素个数 name:键名 redis.scard(‘tags’) 获取键名为 tags 的集合中的元素个数 3 sismember(name, value) 测试 member 是否是键名为 name 的集合的元素 name:键值 redis.sismember(‘tags’, ‘Book’) 判断 Book 是否是键名为 tags 的集合元素 True sinter(keys, *args) 返回所有给定键的集合的交集 keys:键名序列 redis.sinter([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的交集 {b’Coffee’} sinterstore(dest, keys, *args) 求交集并将交集保存到 dest 的集合 dest:结果集合;keys:键名序列 redis.sinterstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的交集并将其保存为 inttag 1 sunion(keys, *args) 返回所有给定键的集合的并集 keys:键名序列 redis.sunion([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的并集 {b’Coffee’, b’Book’, b’Pen’} sunionstore(dest, keys, *args) 求并集并将并集保存到 dest 的集合 dest:结果集合;keys:键名序列 redis.sunionstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的并集并将其保存为 inttag 3 sdiff(keys, *args) 返回所有给定键的集合的差集 keys:键名序列 redis.sdiff([‘tags’, ‘tags2’]) 返回键名为 tags 的集合和键名为 tags2 的集合的差集 {b’Book’, b’Pen’} sdiffstore(dest, keys, *args) 求差集并将差集保存到 dest 集合 dest:结果集合;keys:键名序列 redis.sdiffstore (‘inttag’, [‘tags’, ‘tags2’]) 求键名为 tags 的集合和键名为 tags2 的集合的差集并将其保存为 inttag 3 smembers(name) 返回键名为 name 的集合的所有元素 name:键名 redis.smembers(‘tags’) 返回键名为 tags 的集合的所有元素 {b’Pen’, b’Book’, b’Coffee’} srandmember(name) 随机返回键名为 name 的集合中的一个元素,但不删除元素 name:键值 redis.srandmember(‘tags’) 随机返回键名为 tags 的集合中的一个元素 Srandmember (name) 【16.8】SortedSet(有序集合)操作 方法 作用 参数说明 示例 示例说明 示例结果 zadd(name, args, *kwargs) 向键名为 name 的 zset 中添加元素 member,score 用于排序。如果该元素存在,则更新其顺序 name:键名;args:可变参数 redis.zadd(‘grade’, 100, ‘Bob’, 98, ‘Mike’) 向键名为 grade 的 zset 中添加 Bob(其 score 为 100),并添加 Mike(其 score 为 98) 2,即添加的元素个数 zrem(name, *values) 删除键名为 name 的 zset 中的元素 name:键名;values:元素 redis.zrem(‘grade’, ‘Mike’) 从键名为 grade 的 zset 中删除 Mike 1,即删除的元素个数 zincrby(name, value, amount=1) 如果在键名为 name 的 zset 中已经存在元素 value,则将该元素的 score 增加 amount;否则向该集合中添加该元素,其 score 的值为 amount name:键名;value:元素;amount:增长的 score 值 redis.zincrby(‘grade’, ‘Bob’, -2) 键名为 grade 的 zset 中 Bob 的 score 减 2 98.0,即修改后的值 zrank(name, value) 返回键名为 name 的 zset 中元素的排名,按 score 从小到大排序,即名次 name:键名;value:元素值 redis.zrank(‘grade’, ‘Amy’) 得到键名为 grade 的 zset 中 Amy 的排名 1 zrevrank(name, value) 返回键为 name 的 zset 中元素的倒数排名(按 score 从大到小排序),即名次 name:键名;value:元素值 redis.zrevrank (‘grade’, ‘Amy’) 得到键名为 grade 的 zset 中 Amy 的倒数排名 2 zrevrange(name, start, end, withscores= False) 返回键名为 name 的 zset(按 score 从大到小排序)中 index 从 start 到 end 的所有元素 name:键值;start:开始索引;end:结束索引;withscores:是否带 score redis.zrevrange (‘grade’, 0, 3) 返回键名为 grade 的 zset 中前四名元素 [b’Bob’, b’Mike’, b’Amy’, b’James’] zrangebyscore (name, min, max, start=None, num=None, withscores=False) 返回键名为 name 的 zset 中 score 在给定区间的元素 name:键名;min:最低 score;max:最高 score;start:起始索引;num:个数;withscores:是否带 score redis.zrangebyscore (‘grade’, 80, 95) 返回键名为 grade 的 zset 中 score 在 80 和 95 之间的元素 [b’Bob’, b’Mike’, b’Amy’, b’James’] zcount(name, min, max) 返回键名为 name 的 zset 中 score 在给定区间的数量 name:键名;min:最低 score;max:最高 score redis.zcount(‘grade’, 80, 95) 返回键名为 grade 的 zset 中 score 在 80 到 95 的元素个数 2 zcard(name) 返回键名为 name 的 zset 的元素个数 name:键名 redis.zcard(‘grade’) 获取键名为 grade 的 zset 中元素的个数 3 zremrangebyrank (name, min, max) 删除键名为 name 的 zset 中排名在给定区间的元素 name:键名;min:最低位次;max:最高位次 redis.zremrangebyrank (‘grade’, 0, 0) 删除键名为 grade 的 zset 中排名第一的元素 1,即删除的元素个数 zremrangebyscore (name, min, max) 删除键名为 name 的 zset 中 score 在给定区间的元素 name:键名;min:最低 score;max:最高 score redis.zremrangebyscore (‘grade’, 80, 90) 删除 score 在 80 到 90 之间的元素 1,即删除的元素个数 【16.9】RedisDumpRedisDump 是 Redis 一个数据导入导出工具,是基于 Ruby 实现的,首先访问 Ruby 官网安装对应操作系统的 Ruby:http://www.ruby-lang.org/zh_cn/downloads/ ,安装完成即可使用 gem 命令,该命令类似于 Python 当中的 pip 命令,使用 gem install redis-dump 即可完成 RedisDump 的安装,安装完成后就可以使用导出数据 redis-dump 命令和导入数据 redis-load 命令了 【16.9.1】导出数据 redis-dump在命令行输入 redis-dump -h 可以查看: 123456789101112Usage: E:/Ruby26-x64/bin/redis-dump [global options] COMMAND [command options] -u, --uri=S Redis URI (e.g. redis://hostname[:port]) -d, --database=S Redis database (e.g. -d 15) -a, --password=S Redis password (e.g. -a 'my@pass/word') -s, --sleep=S Sleep for S seconds after dumping (for debugging) -c, --count=S Chunk size (default: 10000) -f, --filter=S Filter selected keys (passed directly to redis' KEYS command) -b, --base64 Encode key values as base64 (useful for binary values) -O, --without_optimizations Disable run time optimizations -V, --version Display version -D, --debug --nosafe 命令解释: -u Redis 连接字符串 -d 数据库代号 -a 数据库密码 -s 导出之后的休眠时间 -c 分块大小,默认是 10000 -f 导出时的过滤器 -b 将键值编码为 base64(对二进制值有用) -O 禁用运行时优化 -V 显示版本 -D 开启调试 导出数据示例: 12345678910111213redis-dump# 指定端口redis-dump -u 127.0.0.1:6379# 指定端口和密码redis-dump -u :password@127.0.0.1:6379# 导出指定数据库redis-dump -u 127.0.0.1:6379 -d 3# 导出包含特定值的数据redis-dump -u 127.0.0.1:6379 -f age 输出示例: 1234567891011121314151617181920212223# 导出所有数据{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2}# 导出 3 号数据库{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}# 导出 key 包含 age 的数据{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 导出所有数据为 JSON 文件: 1redis-dump -u 127.0.0.1:6379 > db_full.json 该命令将会在当前目录生成一个名为 db_full.json 的文件,文件内容如下: 1234567891011121314{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3}{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 使用参数 -d 指定某个数据库的所有数据导出为 JSON 文件: 1redis-dump -u 127.0.0.1:6379 -d 4 > db_db4.json 该命令会将 4 号数据库的数据导出到 db_db4.json 文件: 12{\"db\":4,\"key\":\"age\",\"ttl\":-1,\"type\":\"string\",\"value\":\"20\",\"size\":2}{\"db\":4,\"key\":\"age2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"19\",\"size\":2} 使用参数 -f 过滤数据,只导出特定的数据: 1redis-dump -u 127.0.0.1:6379 -f name > db_name.json 该命令会导出 key 包含 name 的数据到 db_name.json 文件: 123456789101112{\"db\":0,\"key\":\"name5\",\"ttl\":-1,\"type\":\"string\",\"value\":\"DDD\",\"size\":3}{\"db\":0,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":0,\"key\":\"name4\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name6\",\"ttl\":-1,\"type\":\"string\",\"value\":\"CCC\",\"size\":3}{\"db\":0,\"key\":\"name\",\"ttl\":-1,\"type\":\"string\",\"value\":\"TRHX\",\"size\":4}{\"db\":0,\"key\":\"name3\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":1,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":2,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"BBB\",\"size\":3}{\"db\":2,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"AAA\",\"size\":3}{\"db\":3,\"key\":\"name2\",\"ttl\":-1,\"type\":\"string\",\"value\":\"HHH\",\"size\":3}{\"db\":3,\"key\":\"name1\",\"ttl\":-1,\"type\":\"string\",\"value\":\"RRR\",\"size\":3} 【16.9.2】导入数据 redis-load在命令行输入 redis-load -h 可以查看: 123456789redis-load --help Try: redis-load [global options] COMMAND [command options] -u, --uri=S Redis URI (e.g. redis://hostname[:port]) -d, --database=S Redis database (e.g. -d 15) -s, --sleep=S Sleep for S seconds after dumping (for debugging) -n, --no_check_utf8 -V, --version Display version -D, --debug --nosafe 命令解释: -u Redis 连接字符串 -d 数据库代号,默认是全部 -s 导出之后的休眠时间 -n 不检测 UTF-8 编码 -V 显示版本 -D 开启调试 导入示例: 12345# 将 test.json 文件所有内容导入到数据库< test.json redis-load -u 127.0.0.1:6379# 将 test.json 文件 db 值为 6 的数据导入到数据库 < test.json redis-load -u 127.0.0.1:6379 -d 6 另外,以下方法也能导入数据: 12345# 将 test.json 文件所有内容导入到数据库cat test.json | redis-load -u 127.0.0.1:6379# 将 test.json 文件 db 值为 6 的数据导入到数据库 cat test.json | redis-load -u 127.0.0.1:6379 -d 6 注意:cat 是 Linux 系统专有的命令,在 Windows 系统里没有 cat 这个命令,可以使用 Windows 批处理命令 type 代替 cat","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Redis","slug":"Redis","permalink":"https://www.itrhx.com/tags/Redis/"}]},{"title":"Python3 爬虫学习笔记 C15","slug":"A45-Python3-spider-C15","date":"2019-09-10T11:46:13.293Z","updated":"2019-09-24T12:41:02.822Z","comments":true,"path":"2019/09/10/A45-Python3-spider-C15/","link":"","permalink":"https://www.itrhx.com/2019/09/10/A45-Python3-spider-C15/","excerpt":"Python3 爬虫学习笔记第十五章 —— 【代理的基本使用】","text":"Python3 爬虫学习笔记第十五章 —— 【代理的基本使用】 【15.1】代理初识大多数网站都有反爬虫机制,如果一段时间内同一个 IP 发送的请求过多,服务器就会拒绝访问,直接禁封该 IP,此时,设置代理即可解决这个问题,网络上有许多免费代理和付费代理,比如西刺代理,全网代理 IP,快代理等,设置代理需要用到的就是代理 IP 地址和端口号,如果电脑上装有代理软件(例如:酸酸乳SSR),软件一般会在本机创建 HTTP 或 SOCKS 代理服务,直接使用此代理也可以 【15.2】urllib 库使用代理1234567891011121314from urllib.error import URLErrorfrom urllib.request import ProxyHandler, build_openerproxy = '127.0.0.1:1080'proxy_handler = ProxyHandler({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})opener = build_opener(proxy_handler)try: response = opener.open('http://httpbin.org/get') print(response.read().decode('utf8'))except URLError as e: print(e.reason) http://httpbin.org/get 是一个请求测试站点,借助 ProxyHandler 设置代理,参数为字典类型,键名为协议类型,键值为代理,代理的写法:proxy = '127.0.0.1:1080',其中 127.0.0.1 为 IP 地址,1080 为端口号,这里表示本机的代理软件已经在本地 1080 端口创建了代理服务,代理前面需要加上 http 或者 https 协议,当请求的链接为 http 协议时,ProxyHandler 会自动调用 http 代理,同理,当请求的链接为 https 协议时,ProxyHandler 会自动调用 https 代理,build_opener() 方法传入 ProxyHandler 对象来创建一个 opener,调用 open() 方法传入一个 url 即可通过代理访问该链接,运行结果为一个 JSON,origin 字段为此时客户端的 IP 12345678910{ \"args\": {}, \"headers\": { \"Accept-Encoding\": \"identity\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Python-urllib/3.6\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 如果是需要认证的代理,只需要在代理前面加入代理认证的用户名密码即可: 1234567891011121314from urllib.error import URLErrorfrom urllib.request import ProxyHandler, build_openerproxy = 'username:password@127.0.0.1:1080'proxy_handler = ProxyHandler({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})opener = build_opener(proxy_handler)try: response = opener.open('http://httpbin.org/get') print(response.read().decode('utf8'))except URLError as e: print(e.reason) 如果代理是 SOCKS5 类型,需要用到 socks 模块,设置代理方法如下: 扩展:SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问 Internet 网中的服务器,或者使通讯更加安全 123456789101112import socksimport socketfrom urllib import requestfrom urllib.error import URLErrorsocks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)socket.socket = socks.socksockettry: response = request.urlopen('http://httpbin.org/get') print(response.read().decode('utf-8'))except URLError as e: print(e.reason) 【15.3】requests 库使用代理requests 库使用代理只需要传入 proxies 参数即可: 123456789101112import requestsproxy = '127.0.0.1:1080'proxies = ({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ChunkedEncodingError as e: print('Error', e.args) 输出结果: 1234567891011{ \"args\": {}, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 同样的,如果是需要认证的代理,也只需要在代理前面加入代理认证的用户名密码即可: 123456789101112import requestsproxy = 'username:password@127.0.0.1:1080'proxies = ({ 'http': 'http://' + proxy, 'https': 'https://' + proxy})try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ChunkedEncodingError as e: print('Error', e.args) 如果代理是 SOCKS5 类型,需要用到 requests[socks] 模块或者 socks 模块,使用 requests[socks] 模块时设置代理方法如下: 123456789101112import requestsproxy = '127.0.0.1:1080'proxies = { 'http': 'socks5://' + proxy, 'https': 'socks5://' + proxy}try: response = requests.get('http://httpbin.org/get', proxies=proxies) print(response.text)except requests.exceptions.ConnectionError as e: print('Error', e.args) 使用 socks 模块时设置代理方法如下(此类方法为全局设置): 1234567891011import requestsimport socksimport socketsocks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)socket.socket = socks.socksockettry: response = requests.get('http://httpbin.org/get') print(response.text)except requests.exceptions.ConnectionError as e: print('Error', e.args) 【15.4】Selenium 使用代理【15.4.1】Chrome12345678from selenium import webdriverproxy = '127.0.0.1:1080'chrome_options = webdriver.ChromeOptions()chrome_options.add_argument('--proxy-server=http://' + proxy)path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://httpbin.org/get') 通过 ChromeOptions 来设置代理,在创建 Chrome 对象的时候用 chrome_options 参数传递即可,访问目标链接后显示如下信息: 12345678910111213{ \"args\": {}, \"headers\": { \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,zh;q=0.9\", \"Host\": \"httpbin.org\", \"Upgrade-Insecure-Requests\": \"1\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"} 如果是认证代理,则设置方法如下: 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsimport zipfileip = '127.0.0.1'port = 1080username = 'username'password = 'password'manifest_json = \"\"\"{\"version\":\"1.0.0\",\"manifest_version\": 2,\"name\":\"Chrome Proxy\",\"permissions\": [\"proxy\",\"tabs\",\"unlimitedStorage\",\"storage\",\"<all_urls>\",\"webRequest\",\"webRequestBlocking\"],\"background\": {\"scripts\": [\"background.js\"] }}\"\"\"background_js =\"\"\"var config = { mode: \"fixed_servers\", rules: { singleProxy: { scheme: \"http\", host: \"%(ip) s\", port: %(port) s } } }chrome.proxy.settings.set({value: config, scope: \"regular\"}, function() {});function callbackFn(details) { return { authCredentials: {username: \"%(username) s\", password: \"%(password) s\" } }}chrome.webRequest.onAuthRequired.addListener( callbackFn, {urls: [\"<all_urls>\"]}, ['blocking'])\"\"\" % {'ip': ip, 'port': port, 'username': username, 'password': password}plugin_file = 'proxy_auth_plugin.zip'with zipfile.ZipFile(plugin_file, 'w') as zp: zp.writestr(\"manifest.json\", manifest_json) zp.writestr(\"background.js\", background_js)chrome_options = Options()chrome_options.add_argument(\"--start-maximized\")path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'chrome_options.add_extension(plugin_file)browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://httpbin.org/get') 需要在本地创建一个 manifest.json 配置文件和 background.js 脚本来设置认证代理。运行代码之后本地会生成一个 proxy_auth_plugin.zip 文件来保存当前配置 【15.4.1】PhantomJS借助 service_args 参数,也就是命令行参数即可设置代理: 12345678910from selenium import webdriverservice_args = [ '--proxy=127.0.0.1:1080', '--proxy-type=http']path = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)browser.get('http://httpbin.org/get')print(browser.page_source) 运行结果: 12345678910111213<html><head></head><body><pre style=\"word-wrap: break-word; white-space: pre-wrap;\">{ \"args\": {}, \"headers\": { \"Accept\": \"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\", \"Accept-Encoding\": \"gzip, deflate\", \"Accept-Language\": \"zh-CN,en,*\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1\" }, \"origin\": \"168.70.60.141, 168.70.60.141\", \"url\": \"https://httpbin.org/get\"}</pre></body></html> 如果是需要认证的代理,只需要在 service_args 参数加入 –proxy-auth 选项即可: 1234567891011from selenium import webdriverservice_args = [ '--proxy=127.0.0.1:1080', '--proxy-type=http', '--proxy-auth=username:password']path = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)browser.get('http://httpbin.org/get')print(browser.page_source)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"代理","slug":"代理","permalink":"https://www.itrhx.com/tags/代理/"}]},{"title":"Python3 爬虫学习笔记 C14","slug":"A44-Python3-spider-C14","date":"2019-09-07T17:38:41.491Z","updated":"2019-09-24T12:43:31.445Z","comments":true,"path":"2019/09/08/A44-Python3-spider-C14/","link":"","permalink":"https://www.itrhx.com/2019/09/08/A44-Python3-spider-C14/","excerpt":"Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】","text":"Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】 【14.1】关于点触验证码点触验证码是由杭州微触科技有限公司研发的新一代的互联网验证码,使用点击的形式完成验证,采用专利的印刷算法以及加密算法,保证每次请求到的验证图具有极高的安全性,常见的点触验证码如下: 【14.2】点触验证码攻克思路点触验证码相对其他类型验证码比较复杂,如果依靠 OCR 图像识别点触验证码,则识别难度非常大,此时就要用到互联网的验证码服务平台,这些服务平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多 以下以超级鹰打码平台和中国铁路12306官网来做练习 【14.3】模拟登录 12306 — 总体思路首先在超级鹰打码平台注册账号并申请一个软件 ID,官网:http://www.chaojiying.com/ ,先充值一块钱得到 1000 题分,观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1~4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 获取其 Python API:http://www.chaojiying.com/download/Chaojiying_Python.rar ,然后用 Selenium 模拟登陆,获取到验证码,并将验证码发送给超级鹰后台,返回识别图片的坐标,最后模拟点击即可,整个过程的实现由主程序 12306.py 和超级鹰 API chaojiying.py 组成 整个程序包含的函数: 12345678910def __init__(): 初始化 WebDriver、Chaojiying 对象等def crack(): 破解入口、获取、识别验证码、模拟登录def open(): 账号密码输入def get_screenshot(): 整个页面截图def get_touclick_element(): 获取验证码位置def get_position(): 获取验证码坐标def get_touclick_image(): 剪裁验证码部分def get_points(self, captcha_result): 分析超级鹰返回的坐标def touch_click_words(self, locations): 模拟点击符合要求的图片def login(self): 点击登陆按钮,完成模拟登录 整个程序用到的库: 1234567891011import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import Chaojiyingfrom selenium.common.exceptions import TimeoutException 【14.4】主函数123if __name__ == '__main__': crack = CrackTouClick() crack.crack() 【14.5】初始化函数1234567891011121314151617181920USERNAME = '155********'PASSWORD = '***********'CHAOJIYING_USERNAME = '*******'CHAOJIYING_PASSWORD = '*******'CHAOJIYING_SOFT_ID = '********'CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): self.url = 'https://kyfw.12306.cn/otn/resources/login.html' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.email = USERNAME self.password = PASSWORD self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) 定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录链接 url:https://kyfw.12306.cn/otn/resources/login.html ,谷歌浏览器驱动的目录(path),浏览器启动参数,并将相关参数传递给超级鹰 API 【14.6】破解入口函数123456789101112131415161718def crack(self): self.open() image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) locations = self.get_points(result) self.touch_click_words(locations) self.login() try: success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '用户姓名')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print(cc.text) except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() 调用 open() 函数输入账号密码 调用 get_touclick_image() 函数获取验证码图片 利用超级鹰 Python API PostPic() 方法即可把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个 JSON,如果识别成功,典型的返回结果类似于:{'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': '1f8e1d4bef8b11484cb1f1f34299865b'},其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔 调用 get_points() 函数解析超级鹰识别结果 调用 touch_click_words() 函数对符合要求的图片进行点击,然后点击登陆按钮模拟登陆 使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值 【14.7】账号密码输入函数123456789def open(self): self.browser.get(self.url) login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) username.send_keys(self.email) password.send_keys(self.password) 分析页面可知,登陆页面 URL 为:https://kyfw.12306.cn/otn/resources/login.html ,该页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userName 和 J-password,调用 send_keys() 方法输入账号密码 【14.8】页面截图函数1234def get_screenshot(self): screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot 对整个页面进行截图 【14.9】验证码元素查找函数123def get_touclick_element(self): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) return element 同样分析页面,验证码所在位置的 CSS 为 login-pwd-code 【14.10】获取验证码坐标函数1234567def get_position(self): element = self.get_touclick_element() time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] return (top, bottom, left, right) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息 【14.11】验证码剪裁函数123456def get_touclick_image(self, name='12306.png'): top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha 根据验证码的坐标信息,对页面截图进行剪裁,得到验证码部分,将其保存为 12306.png 【14.12】验证码坐标解析函数1234def get_points(self, captcha_result): groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations get_points() 方法将超级鹰的验证码识别结果变成列表的形式 【14.13】验证码模拟点击函数1234def touch_click_words(self, locations): for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform() touch_click_words() 方法通过调用 move_to_element_with_offset() 方法依次传入解析后的坐标,点击即可 【14.14】模拟点击登陆函数123def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click() 分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录 【14.15】效果实现动图最终实现效果图:(关键信息已经过打码处理) 【14.16】完整代码12306.py 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import timefrom io import BytesIOfrom PIL import Imagefrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver import ActionChainsfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom chaojiying import Chaojiying_Clientfrom selenium.common.exceptions import TimeoutExceptionUSERNAME = '155********'PASSWORD = '***********'CHAOJIYING_USERNAME = '***********'CHAOJIYING_PASSWORD = '***********'CHAOJIYING_SOFT_ID = '******'CHAOJIYING_KIND = '9004'class CrackTouClick(): def __init__(self): #登陆 self.url = 'https://kyfw.12306.cn/otn/resources/login.html' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) self.wait = WebDriverWait(self.browser, 20) self.email = USERNAME self.password = PASSWORD self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID) def crack(self): self.open() image = self.get_touclick_image() bytes_array = BytesIO() image.save(bytes_array, format='PNG') result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND) print(result) locations = self.get_points(result) self.touch_click_words(locations) self.login() try: success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭仁侯')) print(success) cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name') print(cc.text) except TimeoutException: self.chaojiying.ReportError(result['pic_id']) self.crack() def open(self): self.browser.get(self.url) login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account'))) login.click() time.sleep(3) username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName'))) password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password'))) username.send_keys(self.email) password.send_keys(self.password) def get_screenshot(self): screenshot = self.browser.get_screenshot_as_png() screenshot = Image.open(BytesIO(screenshot)) return screenshot def get_touclick_element(self): element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code'))) return element def get_position(self): element = self.get_touclick_element() time.sleep(3) location = element.location size = element.size top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width'] return (top, bottom, left, right) def get_touclick_image(self, name='12306.png'): top, bottom, left, right = self.get_position() screenshot = self.get_screenshot() captcha = screenshot.crop((left, top, right, bottom)) captcha.save(name) return captcha def get_points(self, captcha_result): groups = captcha_result.get('pic_str').split('|') locations = [[int(number) for number in group.split(',')] for group in groups] return locations def touch_click_words(self, locations): for location in locations: print(location) ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform() def login(self): submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login'))) submit.click()if __name__ == '__main__': crack = CrackTouClick() crack.crack() chaojiying.py 1234567891011121314151617181920212223242526272829303132333435363738394041424344import requestsfrom hashlib import md5class Chaojiying_Client(object): def __init__(self, username, password, soft_id): self.username = username password = password.encode('utf8') self.password = md5(password).hexdigest() self.soft_id = soft_id self.base_params = { 'user': self.username, 'pass2': self.password, 'softid': self.soft_id, } self.headers = { 'Connection': 'Keep-Alive', 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)', } def PostPic(self, im, codetype): \"\"\" im: 图片字节 codetype: 题目类型 参考 http://www.chaojiying.com/price.html \"\"\" params = { 'codetype': codetype, } params.update(self.base_params) files = {'userfile': ('ccc.jpg', im)} r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers) return r.json() def ReportError(self, im_id): \"\"\" im_id:报错题目的图片ID \"\"\" params = { 'id': im_id, } params.update(self.base_params) r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers) return r.json()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"点触验证码","slug":"点触验证码","permalink":"https://www.itrhx.com/tags/点触验证码/"}]},{"title":"Python3 爬虫学习笔记 C13","slug":"A43-Python3-spider-C13","date":"2019-09-06T19:52:14.161Z","updated":"2019-09-24T12:40:56.234Z","comments":true,"path":"2019/09/07/A43-Python3-spider-C13/","link":"","permalink":"https://www.itrhx.com/2019/09/07/A43-Python3-spider-C13/","excerpt":"Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】","text":"Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】 【13.1】关于滑动验证码滑动验证码属于行为式验证码,需要通过用户的操作行为来完成验证,一般是根据提示用鼠标将滑块拖动到指定的位置完成验证,此类验证码背景图片采用多种图像加密技术,且添加了很多随机效果,能有效防止OCR文字识别,另外,验证码上的文字采用了随机印刷技术,能够随机采用多种字体、多种变形的实时随机印刷,防止暴力破解;斗鱼、哔哩哔哩、淘宝等平台都使用了滑动验证码 【13.2】滑动验证码攻克思路利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证,首先要分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片,通过对比原始的图片和带滑块缺口的图片的像素,像素不同的地方就是缺口位置,计算出滑块缺口的位置,得到所需要滑动的距离,最后利用 Selenium 进行对滑块的拖拽,拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功 以下以哔哩哔哩为例来做模拟登录练习 【13.3】模拟登录 bilibili — 总体思路首先使用 Selenium 模拟登陆 bilibili,自动输入账号密码,查找到登陆按钮并点击,使其出现滑动验证码,此时分析页面,滑动验证组件是由3个 canvas 组成,分别代表完整图片、带有缺口的图片和需要滑动的图片,3个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,分别获取三张图片时要将其他两张图片设置为 display:none,获取元素位置后即可对图片截图并保存,通过图片像素对比,找到缺口位置即为滑块要移动的距离,随后构造滑动轨迹,按照先加速后减速的方式移动滑块完成验证。 整个程序包含的函数: 1234567891011def init(): 初始化函数,定义全局变量def login(): 登录函数,输入账号密码并点击登录def find_element(): 验证码元素查找函数,查找三张图的元素def hide_element(): 设置元素不可见函数def show_element(): 设置元素可见函数def save_screenshot(): 验证码截图函数,截取三张图并保存def slide(): 滑动函数def is_pixel_equal(): 像素判断函数,寻找缺口位置def get_distance(): 计算滑块移动距离函数def get_track(): 构造移动轨迹函数def move_to_gap(): 模拟拖动函数 整个程序用到的库: 12345678from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport random 【13.4】主函数12345if __name__ == '__main__': init() login() find_element() slide() 【13.5】初始化函数12345678910def init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) username = '155********' password = '***********' wait = WebDriverWait(browser, 20) global 关键字定义了全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时 【13.6】登录函数123456789def login(): browser.get(url) user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) user.send_keys(username) passwd.send_keys(password) login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) time.sleep(random.random() * 3) login_btn.click() 等待用户名输入框和密码输入框对应的 ID 节点加载出来,分析页面可知,用户名输入框 id="login-username",密码输入框 id="login-passwd",获取这两个节点,调用 send_keys() 方法输入用户名和密码,随后获取登录按钮,分析页面可知登录按钮 class="btn btn-login",随机产生一个数并将其扩大三倍作为暂停时间,最后调用 click() 方法实现登录按钮的点击 【13.7】验证码元素查找函数12345678910111213def find_element(): c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) hide_element(c_slice) save_screenshot(c_background, 'back') show_element(c_slice) save_screenshot(c_slice, 'slice') show_element(c_full_bg) save_screenshot(c_full_bg, 'full') 我们要获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片,分析页面代码,这三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片,定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute,随后传值给 save_screenshot() 函数,进一步对验证码进行处理 【13.8】元素可见性设置函数12345678# 设置元素不可见def hide_element(element): browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")# 设置元素可见def show_element(element): browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;") 【13.9】验证码截图函数1234567891011121314151617181920def save_screenshot(obj, name): try: pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg) location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息,首先调用 save_screenshot() 属性对整个页面截图并保存,然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存 【13.10】滑动函数123456def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3) 向 get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证 【13.11】计算滑块移动距离函数123456def get_distance(bg_image, fullbg_image): distance = 60 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): if not is_pixel_equal(fullbg_image, bg_image, i, j): return i get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 像素判断函数判断两张图片同一位置的像素是否相同,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置 【13.12】像素判断函数123456789def is_pixel_equal(bg_image, fullbg_image, x, y): bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] threshold = 60 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return False 将完整图片和缺口图片两个对象分别赋值给变量 bg_image和 fullbg_image,接下来对比图片获取缺口。我们在这里遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,如果二者的 RGB 数据差距在一定范围内,那就代表两个像素相同,继续比对下一个像素点,如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置 【13.13】构造移动轨迹函数123456789101112131415def get_trace(distance): trace = [] faster_distance = distance * (4 / 5) start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 20 else: a = -20 move = v0 * t + 1 / 2 * a * t * t v = v0 + a * t v0 = v start += move trace.append(round(move)) return trace get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系: 12move = v0 * t + 0.5 * a * t * t v = v0 + a * t 设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为20和-20,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了 【13.14】模拟拖动函数1234567def move_to_gap(trace): slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) ActionChains(browser).click_and_hold(slider).perform() for x in trace: ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(browser).release().perform() 传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可 【13.15】效果实现动图最终实现效果图:(关键信息已经过打码处理) 【13.16】完整代码123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom selenium.webdriver.support.wait import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECfrom selenium.webdriver.common.by import Byfrom selenium.webdriver import ActionChainsimport timeimport randomfrom PIL import Imagedef init(): global url, browser, username, password, wait url = 'https://passport.bilibili.com/login' path = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe' chrome_options = Options() chrome_options.add_argument('--start-maximized') browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options) username = '155********' password = '***********' wait = WebDriverWait(browser, 20)def login(): browser.get(url) user = wait.until(EC.presence_of_element_located((By.ID, 'login-username'))) passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd'))) user.send_keys(username) passwd.send_keys(password) login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login'))) time.sleep(random.random() * 3) login_btn.click()def find_element(): c_background = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute'))) c_slice = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute'))) c_full_bg = wait.until( EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute'))) hide_element(c_slice) save_screenshot(c_background, 'back') show_element(c_slice) save_screenshot(c_slice, 'slice') show_element(c_full_bg) save_screenshot(c_full_bg, 'full')def hide_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: none;\")def show_element(element): browser.execute_script(\"arguments[0].style=arguments[1]\", element, \"display: block;\")def save_screenshot(obj, name): try: pic_url = browser.save_screenshot('.\\\\bilibili.png') print(\"%s:截图成功!\" % pic_url) left = obj.location['x'] top = obj.location['y'] right = left + obj.size['width'] bottom = top + obj.size['height'] print('图:' + name) print('Left %s' % left) print('Top %s' % top) print('Right %s' % right) print('Bottom %s' % bottom) print('') im = Image.open('.\\\\bilibili.png') im = im.crop((left, top, right, bottom)) file_name = 'bili_' + name + '.png' im.save(file_name) except BaseException as msg: print(\"%s:截图失败!\" % msg)def slide(): distance = get_distance(Image.open('.\\\\bili_back.png'), Image.open('.\\\\bili_full.png')) print('计算偏移量为:%s Px' % distance) trace = get_trace(distance - 5) move_to_gap(trace) time.sleep(3)def get_distance(bg_image, fullbg_image): distance = 60 for i in range(distance, fullbg_image.size[0]): for j in range(fullbg_image.size[1]): if not is_pixel_equal(fullbg_image, bg_image, i, j): return idef is_pixel_equal(bg_image, fullbg_image, x, y): bg_pixel = bg_image.load()[x, y] fullbg_pixel = fullbg_image.load()[x, y] threshold = 60 if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs( bg_pixel[2] - fullbg_pixel[2] < threshold)): return True else: return Falsedef get_trace(distance): trace = [] faster_distance = distance * (4 / 5) start, v0, t = 0, 0, 0.1 while start < distance: if start < faster_distance: a = 20 else: a = -20 move = v0 * t + 1 / 2 * a * t * t v = v0 + a * t v0 = v start += move trace.append(round(move)) return tracedef move_to_gap(trace): slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button'))) ActionChains(browser).click_and_hold(slider).perform() for x in trace: ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform() time.sleep(0.5) ActionChains(browser).release().perform()if __name__ == '__main__': init() login() find_element() slide()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"滑动验证码","slug":"滑动验证码","permalink":"https://www.itrhx.com/tags/滑动验证码/"}]},{"title":"Python3 爬虫学习笔记 C12","slug":"A42-Python3-spider-C12","date":"2019-09-05T14:54:48.887Z","updated":"2019-09-24T12:40:54.127Z","comments":true,"path":"2019/09/05/A42-Python3-spider-C12/","link":"","permalink":"https://www.itrhx.com/2019/09/05/A42-Python3-spider-C12/","excerpt":"Python3 爬虫学习笔记第十二章 —— 【验证码对抗系列 — 图形验证码】","text":"Python3 爬虫学习笔记第十二章 —— 【验证码对抗系列 — 图形验证码】 【12.1】关于普通图形验证码普通图形验证码一般由四位纯数字、纯字母或者字母数字组合构成,是最常见的验证码,也是最简单的验证码,利用 tesserocr 或者 pytesseract 库即可识别此类验证码,前提是已经安装好 Tesseract-OCR 软件 【12.2】tesserocr 库识别验证码简单示例: 123456import tesserocrfrom PIL import Imageimage = Image.open('code.png')result = tesserocr.image_to_text(image)print(result) 新建一个 Image 对象,调用 tesserocr 的 image_to_text() 方法,传入 Image 对象即可完成识别,另一种方法: 12import tesserocrprint(tesserocr.file_to_text('code.png')) 【12.3】pytesseract 库识别验证码简单示例: 1234567import pytesseractfrom PIL import Imageimg = Image.open('code.png')img = img.convert('RGB')img.show()print(pytesseract.image_to_string(img)) pytesseract 的各种方法: get_tesseract_version:返回 Tesseract 的版本信息; image_to_string:将图像上的 Tesseract OCR 运行结果返回到字符串; image_to_boxes:返回包含已识别字符及其框边界的结果; image_to_data:返回包含框边界,置信度和其他信息的结果。需要 Tesseract 3.05+; image_to_osd:返回包含有关方向和脚本检测的信息的结果。 有关参数: image_to_data(image, lang='', config='', nice=0, output_type=Output.STRING) image:图像对象; lang:Tesseract 语言代码字符串; config:任何其他配置为字符串,例如:config=’–psm 6’; nice:修改 Tesseract 运行的处理器优先级。Windows不支持。尼斯调整了类似 unix 的流程的优点; output_type:类属性,指定输出的类型,默认为string。 lang 参数,常见语言代码如下: chi_sim:简体中文 chi_tra:繁体中文 eng:英文 rus:俄罗斯语 fra:法语 deu:德语 jpn:日语 【12.4】验证码处理利用 Image 对象的 convert() 方法传入不同参数可以对验证码做一些额外的处理,如转灰度、二值化等操作,经过处理过后的验证码会更加容易被识别,识别准确度更高,各种参数及含义: 1:1位像素,黑白,每字节一个像素存储; L:8位像素,黑白; P:8位像素,使用调色板映射到任何其他模式; RGB:3x8位像素,真彩色; RGBA:4x8位像素,带透明度掩模的真彩色; CMYK:4x8位像素,分色; YCbCr:3x8位像素,彩色视频格式; I:32位有符号整数像素; F:32位浮点像素。 示例: 12345678import pytesseractfrom PIL import Imageimage = Image.open('code.png')image = image.convert('L')image.show()result = pytesseract.image_to_string(image)print(result) Image 对象的 convert() 方法参数传入 L,即可将图片转化为灰度图像,转换前后对比: 12345678import pytesseractfrom PIL import Imageimage = Image.open('code.png')image = image.convert('1')image.show()result = pytesseract.image_to_string(image)print(result) Image 对象的 convert() 方法参数传入 1,即可将图片进行二值化处理,处理前后对比: 【12.5】tesserocr 与 pytesserocr 相关资料 tesserocr GitHub:https://github.com/sirfz/tesserocr tesserocr PyPI:https://pypi.python.org/pypi/tesserocr pytesserocr GitHub:https://github.com/madmaze/pytesseract pytesserocr PyPI:https://pypi.org/project/pytesseract/ Tesseract-OCR 下载地址:http://digi.bib.uni-mannheim.de/tesseract tesseract GitHub:https://github.com/tesseract-ocr/tesseract tesseract 语言包:https://github.com/tesseract-ocr/tessdata tesseract 文档:https://github.com/tesseract-ocr/tesseract/wiki/Documentation","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"图形验证码","slug":"图形验证码","permalink":"https://www.itrhx.com/tags/图形验证码/"}]},{"title":"Python3 爬虫学习笔记 C11","slug":"A41-Python3-spider-C11","date":"2019-09-04T14:06:03.110Z","updated":"2019-09-24T12:40:45.074Z","comments":true,"path":"2019/09/04/A41-Python3-spider-C11/","link":"","permalink":"https://www.itrhx.com/2019/09/04/A41-Python3-spider-C11/","excerpt":"Python3 爬虫学习笔记第十一章 —— 【MongoDB数据储存】","text":"Python3 爬虫学习笔记第十一章 —— 【MongoDB数据储存】 【11.1】关于 MongoDBMongoDB 属于非关系型数据库,即 NoSQL(Not Only SQL),NoSQL 是基于键值对的,不需要经过 SQL 层的解析,数据之间没有耦合性,性能极高,非关系型数据库分为以下几种: 键值存储数据库:Redis、Voldemort、Oracle BDB 等; 列存储数据库:Cassandra、HBase、Riak 等; 文档型数据库:CouchDB、MongoDB 等; 图形数据库:Neo4J、InfoGrid、Infinite Graph 等。 【11.2】MongoDB 基本操作语句123456789101112131415161718192021222324252627282930# 创建数据库(如果数据库不存在就创建数据库, 存在就切换到指定的数据库)use DATABASE_NAME# 查看所有数据库show dbs# 查看当前所在数据库db# 删除当前数据库db.dropDatabase()# 删除集合db.COLLECTION_NAME.drop()# 创建集合db.createCollection(\"COLLECTION_NAME\")# 插入文档db.COLLECTION_NAME.insert(document)db.COLLECTION_NAME.save(document) # 更新文档db.COLLECTION_NAME.update()# 删除文档db.COLLECTION_NAME.remove()# 查询文档db.COLLECTION_NAME.find(query, projection) 【11.3】连接 MongoDB连接 MongoDB 需要导入 pymongo 库,使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port 即可 123import pymongoclient = pymongo.MongoClient(host='localhost', port=27017) 也可以直接传入 MongoDB 的连接字符串: 123import pymongoclient = pymongo.MongoClient('mongodb://localhost:27017/') 【11.4】指定数据库使用以下语句皆可指定一个名为 spiders 的数据库: 1db = client.spiders 1db = client['spiders'] 【11.5】指定集合MongoDB 的每个数据库包含多个集合(collection),类似于关系型数据库 MySQL 中的数据表,使用以下语句皆可指定一个名为 students 的集合: 1collection = db.students 1collection = db['students'] 【11.6】插入数据12345678910111213import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}result = collection.insert(students)print(result) 在 spiders 数据库的 students 集合里,新建一条学生数据,该数据以字典形式表示,调用 collection 的 insert() 方法插入数据,在 MongoDB 中,每条数据都有一个_id 属性来唯一标识。如果没有显式指明该属性,MongoDB 会自动产生一个 ObjectId 类型的_id 属性。insert() 方法会在执行后返回 _id 值,在 MongoDB 数据库里面可以看到已经成功插入数据,输出结果: 15d6f1a4b57b65e1547bb3c24 进阶操作:同时插入多条数据,以列表形式传递: 12345678910111213141516171819import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents1 = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}students2 = { 'id': '17110106', 'name': 'AAAA', 'age': 22, 'gender': 'male'}result = collection.insert([students1, students2])print(result) 输出结果: 1[ObjectId('5d6f2be3cd1721962218a709'), ObjectId('5d6f2be3cd1721962218a70a')] PyMongo 3.x 及以上版本中,推荐使用 insert_one() 和 insert_many() 方法来分别插入单条记录和多条记录,示例: 插入单条记录 1234567891011121314import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}result = collection.insert_one(students)print(result)print(result.inserted_id) 返回的是 InsertOneResult 对象,调用其 inserted_id 属性获取_id: 12<pymongo.results.InsertOneResult object at 0x0000020ED91A5608>5d6f73940fe700c5a7ac19f0 插入多条记录 1234567891011121314151617181920import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsstudents1 = { 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}students2 = { 'id': '17110106', 'name': 'AAAA', 'age': 22, 'gender': 'male'}result = collection.insert_many([students1, students2])print(result)print(result.inserted_ids) 返回的类型是 InsertManyResult,调用 inserted_ids 属性可以获取插入数据的_id 列表: 12<pymongo.results.InsertManyResult object at 0x0000021698DD36C8>[ObjectId('5d6f68598fa881c69b2e0006'), ObjectId('5d6f68598fa881c69b2e0007')] 【11.6】数据查询事先已经创建好 spiders 数据库和 students 集合,包含以下数据: 1234567891011121314151617181920212223_id:ObjectId(\"5d6f95d40828142f1dc35fa5\")id:\"17110105\"name:\"TRHX\"age:20gender:\"male\"_id:ObjectId(\"5d6f95d40828142f1dc35fa6\")id:\"17110106\"name:\"AAA\"age:20gender:\"male\"_id:ObjectId(\"5d6f95d40828142f1dc35fa7\")id:\"17110107\"name:\"BBB\"age:19gender:\"female\"_id:ObjectId(\"5d6f95d40828142f1dc35fa8\")id:\"17110108\"name:\"CCC\"age:22gender:\"male\" 查询方法一:利用 find_one() 或 find() 方法进行查询, find_one() 查询得到的是单个结果,find() 则返回一个生成器对象 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find_one({'name': 'TRHX'})print(result) 查询 name 为 TRHX 的数据,返回一个字典类型: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'} 查询方法二:根据 ObjectId 查询,查询时需要使用 bson 库里面的 objectid: 12345678import pymongofrom bson.objectid import ObjectIdclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find_one({'_id': ObjectId('5d6f95d40828142f1dc35fa7')})print(result) 查询结果: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa7'), 'id': '17110107', 'name': 'BBB', 'age': 19, 'gender': 'female'} 使用 find() 方法查询多条数据: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'gender': 'male'})print(results)for result in results: print(result) find() 方法返回一个生成器对象,遍历得到所有数据,每条数据都是字典类型: 1234<pymongo.cursor.Cursor object at 0x00000191F69AAA90>{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'} 在查询条件中加入比较符号进行查询,以下代码实现了年龄大于等于20的数据查询: 12345678import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'age': {'$gte': 20}})for result in results: print(result) 符号 $gte 表示大于等于,查询结果如下: 123{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}{'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'} 附表:各种比较符号 符号 含义 示例 $lt 小于 {‘age’: {‘$lt’: 20}} $gt 大于 {‘age’: {‘$gt’: 20}} $lte 小于等于 {‘age’: {‘$lte’: 20}} $gte 大于等于 {‘age’: {‘$gte’: 20}} $ne 不等于 {‘age’: {‘$ne’: 20}} $in 在范围内 {‘age’: {‘$in’: [20, 23]}} $nin 不在范围内 {‘age’: {‘$nin’: [20, 23]}} 在查询条件中加入功能符号进行查询,以下代码用正则匹配实现了对名字以 T 开头的学生数据的查询: 12345678import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresults = collection.find({'name': {'$regex': '^T.*'}})for result in results: print(result) 查询结果: 1{'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'} 附表:各种功能符号 符号 含义 示例 示例含义 $regex 匹配正则表达式 {‘name’: {‘$regex’: ‘^T.*’}} name 以 T 开头 $exists 属性是否存在 {‘name’: {‘$exists’: True}} name 属性存在 $type 类型判断 {‘age’: {‘$type’: ‘int’}} age 的类型为 int $mod 数字模操作 {‘age’: {‘$mod’: [5, 0]}} 年龄模 5 余 0 $text 文本查询 {‘$text’: {‘$search’: ‘Mike’}} text 类型的属性中包含 Mike 字符串 $where 高级条件查询 {‘$where’: ‘obj.fans_count == obj.follows_count’} 自身粉丝数等于关注数 其他操作:https://docs.mongodb.com/manual/reference/operator/query/ 【11.7】数据计数调用 count() 方法可以统计查询结果有多少条数据,输出结果为一个整数: 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.find({'name': {'$regex': '^T.*'}}).count()print(result) 【11.8】数据排序调用 sort() 方法,向其传入排序的字段及升降序标志即可完成排序: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsascending = collection.find().sort('name', pymongo.ASCENDING)descending = collection.find().sort('name', pymongo.DESCENDING)print('升序排列:', [result['name'] for result in ascending])print('降序排列:', [result['name'] for result in descending]) 输出结果: 12升序排列: ['AAA', 'BBB', 'CCC', 'TRHX']降序排列: ['TRHX', 'CCC', 'BBB', 'AAA'] 【11.9】数据偏移利用 skip() 方法偏移几个位置,就可以跳过前几条数据,获取偏移量之后的几个数据;利用 limit() 方法指定获取前几条数据: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsascending = collection.find().sort('name', pymongo.ASCENDING).skip(1)descending = collection.find().sort('name', pymongo.DESCENDING).limit(2)print('升序排列(偏移量为1,获取后三条数据):', [result['name'] for result in ascending])print('降序排列(限制获取前两条数据):', [result['name'] for result in descending]) 输出结果: 12升序排列(偏移量为1,获取后三条数据): ['BBB', 'CCC', 'TRHX']降序排列(限制获取前两条数据): ['TRHX', 'CCC'] 【11.10】更新数据使用 update() 方法,指定更新的条件和更新后的数据即可: 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 18result = collection.update(condition, student)print(result) 该代码将 name 为 TRHX 的 age 改为了 18,返回结果仍然是字典形式,ok 代表执行成功,nModified 代表影响的数据条数: 1{'n': 1, 'nModified': 1, 'ok': 1.0, 'updatedExisting': True} 进阶操作:使用 $set 操作符对数据进行更新,指定更新的条件和更新后的数据即可,这样做的好处是:只更新指定的 student 字典内存在的字段,如果原先还有其他字段,则不会更新,也不会删除;如果不用 $set ,则会把之前的数据全部用 student 字典替换,如果原本存在其他字段,则会被删除 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 18result = collection.update(condition, {'$set': student})print(result) 和插入数据的 insert() 方法一样,在 PyMongo 3.x 版本里,推荐使用 update_one() 和 update_many() 方法 1234567891011import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'name': 'TRHX'}student = collection.find_one(condition)student['age'] = 19result = collection.update_one(condition, {'$set': student})print(result)print(result.matched_count, result.modified_count) 注意:update_one() 方法不能直接传入修改后的字典,只能使用 {'$set': student} 的形式传入,可以调用 matched_count 和 modified_count 属性,获取匹配的数据条数和影响的数据条数: 12<pymongo.results.UpdateResult object at 0x00000235A1684508>1 1 使用update_many() 方法可以将所有符合条件的数据都更新: 123456789import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentscondition = {'age': {'$gt': 18}}result = collection.update_many(condition, {'$set': {'age': 25}})print(result)print(result.matched_count, result.modified_count) 匹配所有年龄大于 18 的数据,更新条件为将这些所有满足条件的年龄都设置成 25,输出结果如下: 12<pymongo.results.UpdateResult object at 0x00000285CECC45C8>4 4 【11.11】删除数据调用 remove() 方法并指定删除的条件,此时符合条件的所有数据均会被删除 1234567import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.remove({'name': 'CCC'})print(result) 输出结果: 1{'n': 1, 'ok': 1.0} 同样的,在 PyMongo 3.x 版本里,推荐使用 delete_one() 和 delete_many() 方法 12345678910import pymongoclient = pymongo.MongoClient(host='localhost', port=27017)db = client.spiderscollection = db.studentsresult = collection.delete_one({'name': 'AAA'})print(result)print(result.deleted_count)result = collection.delete_many({'gender': 'female'})print(result.deleted_count) 调用 deleted_count 属性可以获取删除的数据条数,输出结果: 123<pymongo.results.DeleteResult object at 0x0000024441B245C8>11 PyMongo 官方文档:http://api.mongodb.com/python/current/api/pymongo/collection.html","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"MongoDB","slug":"MongoDB","permalink":"https://www.itrhx.com/tags/MongoDB/"}]},{"title":"Python3 爬虫学习笔记 C10","slug":"A40-Python3-spider-C10","date":"2019-09-03T15:39:27.326Z","updated":"2020-03-14T06:21:36.668Z","comments":true,"path":"2019/09/03/A40-Python3-spider-C10/","link":"","permalink":"https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/","excerpt":"Python3 爬虫学习笔记第十章 —— 【MySQL数据储存】","text":"Python3 爬虫学习笔记第十章 —— 【MySQL数据储存】 【10.1】MySQL 基本操作语句安装完 MySQL 后,打开 MySQL x.x Command Line Client - Unicode,输入密码即可登录 MySQL,也可在 MySQL 安装目录下打开 cmd 使用命令登录数据库 数据库操作1234567891011121314151617# 连接数据库mysql -u root -p# 退出数据库exit# 查看所有的数据库SHOW DATABASES;# 创建一个数据库CREATE DATABASE X;# 删除一个数据库DROP DATABASE IF EXISTS X;# 使用这个数据库USE X; 表操作12345678910111213141516171819202122232425262728293031323334353637# 查看所有的表SHOW TABLES ;# 创建一个表CREATE TABLE n(id INT, name VARCHAR(10));CREATE TABLE m(id INT, name VARCHAR(10), PRIMARY KEY (id), FOREIGN KEY (id) REFERENCES n(id), UNIQUE (name));CREATE TABLE m(id INT, name VARCHAR(10));# 直接将查询结果导入或复制到新创建的表CREATE TABLE n SELECT * FROM m;# 新创建的表与一个存在的表的数据结构类似CREATE TABLE m LIKE n;# 创建一个临时表# 临时表将在你连接MySQL期间存在。当断开连接时,MySQL将自动删除表并释放所用的空间。也可手动删除。CREATE TEMPORARY TABLE l(id INT, name VARCHAR(10));# 直接将查询结果导入或复制到新创建的临时表CREATE TEMPORARY TABLE tt SELECT * FROM n;# 删除一个存在表DROP TABLE IF EXISTS m;# 更改存在表的名称ALTER TABLE n RENAME m;RENAME TABLE n TO m;# 查看表的结构(以下五条语句效果相同)DESC n;DESCRIBE n;SHOW COLUMNS IN n;SHOW COLUMNS FROM n;EXPLAIN n;# 查看表的创建语句SHOW CREATE TABLE n; 表的结构12345678910111213141516171819202122232425# 添加字段ALTER TABLE n ADD age VARCHAR(2);# 添加字段时设定位置ALTER TABLE n ADD age VARCHAR(2) FIRST;ALTER TABLE n ADD age VARCHAR(2) AFTER name;# 修改字段在表中的位置ALTER TABLE n MODIFY age VARCHAR(2) AFTER name;# 删除字段ALTER TABLE n DROP age;# 更改字段属性和属性ALTER TABLE n CHANGE age a INT;# 只更改字段属性ALTER TABLE n MODIFY age VARCHAR(7) ;# 改变表的存储引擎ALTER TABLE t ENGINE myisam;ALTER TABLE t ENGINE innodb;# 设定自增 初始为1,只能一个字段使用,该字段为主键的一部分ALTER TABLE t AUTO_INCREMENT = 0; 表的数据123456789101112131415# 增加数据INSERT INTO n VALUES (1, 'tom', '23'), (2, 'john', '22');INSERT INTO n SELECT * FROM n; # 把数据复制一遍重新插入# 删除数据DELETE FROM n WHERE id = 2;# 更改数据UPDATE n SET name = 'tom' WHERE id = 2;# 数据查找SELECT * FROM n WHERE name LIKE '%h%';# 数据排序(反序)SELECT * FROM n ORDER BY name, id DESC ; 【10.2】Python 连接 MySQL123456789import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306)cursor = db.cursor()cursor.execute('SELECT VERSION()')data = cursor.fetchone()print('Database version:', data)cursor.execute(\"CREATE DATABASE spiders DEFAULT CHARACTER SET utf8mb4\")db.close() 通过 PyMySQL 的 connect 方法声明一个 MySQL 连接对象 db,当前 MySQL 数据库运行在本地,设定 host='localhost',用户名为 root,登录密码为 000000,运行在 3306 端口,调用 cursor() 方法获得 MySQL 的操作游标,该游标用来执行 SQL 语句,通过游标操作 execute() 方法写入 SQL 语句,第一条 SQL 语句获取 MySQL 的版本信息,调用 fetchone() 方法获得第一条数据,即 MySQL 的版本号。第二条 SQL 语句执行创建 spiders 数据库的操作,编码为 utf8mb4,运行该段代码将输出 MySQL 的版本号: 1Database version: ('8.0.17',) 【10.3】创建表1234567import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL, PRIMARY KEY (id))'cursor.execute(sql)db.close() 该段代码实现了在 spiders 数据库里创建了一个名为 students 的表,包含 id、name、age 三个字段,类型依次为 varchar、varchar、int 【10.4】插入数据1234567891011121314import pymysqlid = '17110105'user = 'TRH'age = 20db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'try: cursor.execute(sql, (id, user, age)) db.commit()except: db.rollback()db.close() commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏,使用查询语句可以看到已经插入的数据: 进阶操作:将需要插入的数据构造成一个字典,这样的做法可以让插入方法无需改动,只需要传入一个动态变化的字典就行了,改写原来的代码如下: 123456789101112131415161718192021import pymysqldata = { 'id': '17110105', 'name': 'TRH', 'age': 20}table = 'students'keys = ', '.join(data.keys())values = ', '.join(['%s']*len(data))db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)try: cursor.execute(sql, tuple(data.values())) print('数据插入成功!') db.commit()except: print('数据插入失败!') db.rollback()db.close() 传入的数是字典,将其定义为 data 变量,表名定义成变量 table,构造插入的字段 id、name 和 age。', '.join(data.keys()) 的结果就是 id, name, age,接着需要构造多个 %s 当作占位符,有三个字段,就需要构造 %s, %s, %s。首先定义长度为 1 的数组 ['%s'],然后用乘法将其扩充为 ['%s', '%s', '%s'],再调用 join() 方法,最终变成 %s, %s, %s。再利用字符串的 format() 方法将表名、字段名和占位符构造出来。最终的 SQL 语句就被动态构造成了如下语句: 1INSERT INTO students(id, name, age) VALUES (%s, %s, %s) 【10.5】更新数据1234567891011121314151617181920212223import pymysqldata = { 'id': '17110105', 'name': 'TRH', 'age': 21}table = 'students'keys = ', '.join(data.keys())values = ', '.join(['%s']*len(data))db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE'.format(table=table, keys=keys, values=values)update = ','.join([\"{key} = % s\".format(key=key) for key in data])sql += updatetry: if cursor.execute(sql, tuple(data.values())*2): print('数据插入成功!') db.commit()except: print('数据插入失败!') db.rollback()db.close() ON DUPLICATE KEY UPDATE 表示如果主键已经存在,就执行更新操作,最终被构造成如下语句: 1INSERT INTO students(id, name, age) VALUES (% s, % s, % s) ON DUPLICATE KEY UPDATE id = % s, name = % s, age = % s 【10.6】删除数据123456789101112131415import pymysqltable = 'students'condition = 'age = 20'db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'DELETE FROM {table} WHERE {condition}'.format(table=table, condition=condition)try: cursor.execute(sql) print('数据删除成功!') db.commit()except: print('数据删除失败!') db.rollback()db.close() 删除操作直接使用 DELETE 语句,指定要删除的目标表名和删除条件即可 【10.7】查询数据123456789101112131415161718import pymysqltable = 'students'db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')cursor = db.cursor()sql = 'SELECT * FROM students WHERE age >= 20'try: cursor.execute(sql) print('Count:', cursor.rowcount) one = cursor.fetchone() print('One:', one) results = cursor.fetchall() print('Results:', results) print('Results Type:', type(results)) for row in results: print(row)except: print('查询失败!') sql = 'SELECT * FROM students WHERE age >= 20':构造一条 SQL 语句,将年龄 大于等于20 岁的学生查询出来 cursor.rowcount:调用 cursor 的 rowcount 属性获取查询结果的条数 cursor.fetchone():调用 cursor 的 fetchone() 方法,获取结果的第一条数据,返回结果是元组形式,元组的元素顺序跟字段一一对应,即第一个元素就是第一个字段 id,第二个元素就是第二个字段 name,以此类推 cursor.fetchall():调用 cursor 的 fetchall() 方法,得到结果的所有数据,它是二重元组,每个元素都是一条记录,本例中显示的是 3 条数据而不是 4 条,这是因为它的内部实现有一个偏移指针用来指向查询结果,最开始偏移指针指向第一条数据,取一次之后,指针偏移到下一条数据,这样再取的话,就会取到下一条数据了。我们最初调用了一次 fetchone 方法,这样结果的偏移指针就指向下一条数据,fetchall 方法返回的是偏移指针指向的数据一直到结束的所有数据,所以该方法获取的结果就只剩 3 个了 【10.8】实战训练 — 爬取CSDN博客标题和地址保存到 MySQL利用 requests 库构建请求,BeautifulSoup 解析库解析网页,获取自己博客文章的标题和地址,将其储存到本地 MySQL 数据库中,事先已经创建好了一个 blog 数据库,并创建了一个名为 article 的数据表,数据表包含 id、title、url 三个字段,其中 id 的 AUTO_INCREMENT 属性可以使 id 自己增加,PRIMARY KEY 关键字用于将 id 定义为主键 创建 article 数据表: 1234567import pymysqldb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')cursor = db.cursor()sql = 'CREATE TABLE IF NOT EXISTS article (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, PRIMARY KEY (id))'cursor.execute(sql)db.close() 获取文章标题和对应的 URL 并将其储存到 MySQL 中: 123456789101112131415161718192021222324import requestsimport pymysqlfrom bs4 import BeautifulSoupdb = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')cursor = db.cursor()headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}url = \"https://blog.csdn.net/qq_36759224\"request = requests.get(url, headers=headers)soup = BeautifulSoup(request.text, 'lxml')title_list = soup.find_all('h4')for list in title_list: s = list.a.text.strip() title = s.replace('原', '') url = list.a['href'].strip() # print(title + url) cursor.execute('INSERT INTO article (title, url) VALUES (%s, %s)', (title, url))db.commit()print('数据写入完毕!')db.close() 在命令行中使用 SELECT * FROM article; 命令可以查看到数据已经成功获取并储存到了数据库中:","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"MySQL","slug":"MySQL","permalink":"https://www.itrhx.com/tags/MySQL/"}]},{"title":"Python3 爬虫学习笔记 C09","slug":"A39-Python3-spider-C09","date":"2019-08-27T10:58:37.295Z","updated":"2019-10-09T10:40:08.565Z","comments":true,"path":"2019/08/27/A39-Python3-spider-C09/","link":"","permalink":"https://www.itrhx.com/2019/08/27/A39-Python3-spider-C09/","excerpt":"Python3 爬虫学习笔记第九章 —— 【文件储存】","text":"Python3 爬虫学习笔记第九章 —— 【文件储存】 用解析器解析出数据之后,还需要对数据进行保存。保存的形式多种多样,最简单的形式是直接保存为文本文件,如 TXT、JSON、CSV 等。 【9.1】TXT 文本存储TXT 文本存储的优点:操作非常简单,TXT 文本几乎兼容任何平台;缺点:不利于检索。 【9.1.1】基本示例1234567891011121314import requestsfrom lxml import etreeheaders = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}url = \"https://blog.csdn.net/qq_36759224\"request = requests.get(url, headers=headers)tree = etree.HTML(request.text)title_list = tree.xpath('//h4/a/text()')for title in title_list: with open('blog.txt', 'a', encoding='utf8') as fp: fp.write(title) 代码实现了我的 CSDN 博客首页所有博文标题的爬取,利用 requests 请求库发送请求,获取响应,用 XPath 获取每一篇博文的标题,然后写入 blog.txt 文件中: 123456789101112131415161718192021帝都的凛冬 最新屏蔽 CSDN 广告方法,专注阅读学习! 使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】 Python3 爬虫学习笔记 C08【解析库 Beautiful Soup】 Python3 爬虫学习笔记 C07 【解析库 lxml】 Python3 爬虫学习笔记 C06 【正则表达式】 Python3 爬虫学习笔记 C05 【Selenium + 无界面浏览器】 Python3 已经安装相关库,Pycharm 仍然报错 ModuleNotFoundError: No module named 'xxxxxx' 的解决办法 Windows/Android/iOS 等常见 User-Agent 大全 Selenium 显式等待条件及其含义 Python3 爬虫学习笔记 C04 【自动化测试工具 Selenium】 Python3 爬虫学习笔记 C03 【Ajax 数据爬取】 Python3 爬虫学习笔记 C02 【基本库 requests 的使用】 Python3 爬虫学习笔记 C01 【基本库 urllib 的使用】 利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS 光学字符识别 Tesseract-OCR 的下载、安装和基本用法 Github+jsDelivr+PicGo 打造稳定快速、高效免费图床 利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持 Python 中 if __name__ == '__main__': 的理解 Hexo 博客本地预览报错:Error: listen EADDRINUSE 0.0.0.0:4000 谷歌浏览器检查更新时出错:无法启动更新检查(错误代码为 3: 0x80080005 -- system level) 【9.1.2】打开方式open() 方法的第二个参数为打开方式,不同的打开方式如下: 读写方式 可否读写 若文件不存在 写入方式 r 读取 报错 不可写入 rb 以二进制方式读取 报错 不可写入 r+ 读取 + 写入 报错 覆盖写入 rb+ 以二进制方式读取+写入 报错 覆盖写入 w 写入 创建 覆盖写入 wb 以二进制方式写入 创建 覆盖写入 w+ 读取 + 写入 创建 覆盖写入 wb+ 以二进制方式读取+写入 创建 覆盖写入 a 写入 创建 附加写入 ab 以二进制方式写入 创建 附加写入 a+ 读取 + 写入 创建 附加写入 ab+ 以二进制方式读取+写入 创建 附加写入 【9.2】JSON 文件存储JSON,全称为 JavaScript Object Notation, 即 JavaScript 对象标记,它通过对象和数组的组合来表示数据,构造简洁但是结构化程度非常高,是一种轻量级的数据交换格式。 【9.2.1】对象和数组在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等,但是对象和数组是比较特殊且常用的两种类型 对象:它在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2, …} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。 数组:数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 [“java”, “javascript”, “vb”, …] 的索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引用得多。同样,值的类型可以是任意类型。 示例:一个 JSON 对象 123456789[{ \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}, { \"name\": \"XXX\", \"gender\": \"female\", \"birthday\": \"1999-10-18\"}] 【9.2.2】读取 JSONPython 里面的 JSON 库可以实现对 JSON 文件的读写操作,调用 JSON 库的 loads 方法将 JSON 文本字符串转为 JSON 对象、 dumps() 方法将 JSON 对象转为文本字符串 12345678910111213141516171819import jsonstr = '''[{ \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}, { \"name\": \"XXX\", \"gender\": \"female\", \"birthday\": \"1999-10-18\"}]'''data = json.loads(str)print(data)print(data[0]['name'])print(data[0].get('name'))print(data[0].get('age'))print(data[0].get('age', 25)) 使用 loads 方法将字符串转为 JSON 对象,通过索引来获取对应的内容,获取键值时有两种方式,一种是中括号加键名,另一种是通过 get 方法传入键名。使用 get 方法,如果键名不存在,则不会报错,会返回 None,get 方法还可以传入第二个参数(即默认值),尝试获取一个原字典中不存在的键名,此时默认会返回 None。如果传入第二个参数(即默认值),那么在不存在的情况下返回该默认值。 12345[{'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}, {'name': 'XXX', 'gender': 'female', 'birthday': '1999-10-18'}]TRHTRHNone25 【9.2.3】写入 JSON 文件调用 dumps 方法可以将 JSON 对象转化为字符串,然后再调用文件的 write 方法即可写入文本 123456789import jsondata = [{ 'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}]with open('data.json', 'w') as fp: fp.write(json.dumps(data)) data.json 文件: 1[{\"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\"}] 添加参数 indent(代表缩进字符个数),将会格式化输出:123456789import jsondata = [{ 'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}]with open('data.json', 'w') as file: file.write(json.dumps(data, indent=2)) 输出结果: 1234567[ { \"name\": \"TRH\", \"gender\": \"male\", \"birthday\": \"1999-01-25\" }] 如果 JSON 中包含中文字符,需要指定参数 ensure_ascii 为 False,另外还要规定文件输出的编码: 123456789import jsondata = [{ 'name': '小明', 'gender': '男', 'birthday': '1999年01月25日'}]with open('data.json', 'w', encoding='utf-8') as file: file.write(json.dumps(data, indent=2, ensure_ascii=False)) 输出结果: 1234567[ { \"name\": \"小明\", \"gender\": \"男\", \"birthday\": \"1999年01月25日\" }] 【9.3】CSV 文本存储CSV(Comma-Separated Values)是逗号分隔值或字符分隔值的文件格式,其文件以纯文本的形式储存表格数据(数字和文本),CSV 文件的行与行之间用换行符分隔,列与列之间用逗号分隔 【9.3.1】写入12345678import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile) writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 打开 data.csv 文件,调用 CSV 库的 writer 方法初始化写入对象,然后调用 writerow 方法传入每行的数据即可完成写入,用 Excel 打开 data.csv 文件将是表格形式 1234567id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 默认每一行之间是有一行空格的,可以使用参数 newline 来去除空行: 12345678import csvwith open('data.csv', 'w', newline='') as csvfile: writer = csv.writer(csvfile) writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 输出结果: 1234id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 列与列之间的分隔符是可以修改的,只需要传入 delimiter 参数即可: 12345678import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile, delimiter=' ') writer.writerow(['id', 'name', 'age']) writer.writerow(['10001', 'TRHX', 20]) writer.writerow(['10002', 'Bob', 22]) writer.writerow(['10003', 'Jordan', 21]) 输出结果: 1234567id name age10001 TRHX 2010002 Bob 2210003 Jordan 21 调用 writerows 方法也可以同时写入多行,此时参数就需要为二维列表: 123456import csvwith open('data.csv', 'w') as csvfile: writer = csv.writer(csvfile, delimiter=' ') writer.writerow(['id', 'name', 'age']) writer.writerows([['10001', 'TRHX', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]]) 输出结果仍与原来的一样 此外 CSV 库中也提供了字典的写入方式: 123456789import csvwith open('data.csv', 'w') as csvfile: fieldnames = ['id', 'name', 'age'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) writer.writeheader() writer.writerow({'id': '10001', 'name': 'TRHX', 'age': 20}) writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22}) writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21}) 首先定义 3 个字段,用 fieldnames 表示,然后将其传给 DictWriter 来初始化一个字典写入对象,接着可以调用 writeheader 方法先写入头信息,然后再调用 writerow 方法传入相应字典即可 1234567id,name,age10001,TRHX,2010002,Bob,2210003,Jordan,21 【9.3.2】读取有写入方法,同样也可以使用 csv 库来读取 CSV 文件: 123456import csvwith open('data.csv', 'r', encoding='utf-8') as csvfile: reader = csv.reader(csvfile) for row in reader: print(row) 构造 Reader 对象,遍历输出每行的内容,每一行都是一个列表形式。(如果 CSV 文件中包含中文的话,还需要指定文件编码)读取结果: 1234['id', 'name', 'age']['10001', 'TRHX', '20']['10002', 'Bob', '22']['10003', 'Jordan', '21'] 此外,还可以利用 pandas 的 read_csv 方法将数据从 CSV 中读取出来(pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具) 1234import pandas as pddf = pd.read_csv('data.csv')print(df) 读取结果: 1234 id name age0 10001 TRHX 201 10002 Bob 222 10003 Jordan 21","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"文件储存","slug":"文件储存","permalink":"https://www.itrhx.com/tags/文件储存/"}]},{"title":"Python3 爬虫学习笔记 C08","slug":"A38-Python3-spider-C08","date":"2019-08-26T13:57:58.870Z","updated":"2019-09-24T12:40:34.686Z","comments":true,"path":"2019/08/26/A38-Python3-spider-C08/","link":"","permalink":"https://www.itrhx.com/2019/08/26/A38-Python3-spider-C08/","excerpt":"Python3 爬虫学习笔记第八章 —— 【解析库 Beautiful Soup】","text":"Python3 爬虫学习笔记第八章 —— 【解析库 Beautiful Soup】 【8.1】关于 Beautiful SoupBeautiful Soup 可以从 HTML 或者 XML 文件中提取数据,Beautiful Soup 可以提供一些简单的、Python 式的函数用来处理导航、搜索、修改分析树等,它借助网页的结构和属性等特性来解析网页,lxml 只会局部遍历,而 Beautiful Soup 是基于 HTML DOM 的,会载入整个文档,解析整个 DOM 树,因此时间和内存开销都会大很多,所以性能要低于lxml 抓取工具 速度 使用难度 安装难度 正则 最快 困难 无(内置) lxml 快 简单 一般 BeautifulSoup 慢 最简单 简单 【8.2】Beautiful Soup 的基本使用需要使用命令 pip install bs4 安装库,Beautiful Soup 在解析时依赖解析器,除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器: 解析器 使用方法 优势 劣势 Python 标准库 BeautifulSoup(markup, “html.parser”) Python 的内置标准库、执行速度适中 、文档容错能力强 Python 2.7.3 or 3.2.2) 前的版本中文容错能力差 LXML HTML 解析器 BeautifulSoup(markup, “lxml”) 速度快、文档容错能力强 需要安装 C 语言库 LXML XML 解析器 BeautifulSoup(markup, “xml”) 速度快、唯一支持 XML 的解析器 需要安装 C 语言库 html5lib BeautifulSoup(markup, “html5lib”) 最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档 速度慢、不依赖外部扩展 基本使用:1234from bs4 import BeautifulSoupsoup = BeautifulSoup('<p>Hello</p>', 'lxml')# soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.p.string) 输出结果:1Hello 【8.3】节点选择器直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本 【8.3.1】元素选择新建 soup.html 文件:123456789101112131415161718192021222324252627282930313233343536<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>测试bs4</title></head><body><div> 甄姬 <p>百里守约</p> <p>李白</p> 太乙真人</div><div class=\"song\"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href=\"http://www.song.com/\" title=\"赵匡义\" target=\"_self\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a> <img src=\"http://www.baidu.com/meinv.jpg\" alt=\"\"> <a href=\"\" class=\"du\">总为浮云能蔽日,长安不见使人愁</a></div><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" alt=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><b>唐朝</b></li> <li><i>宋朝</i></li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 1234567from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.title)print(type(soup.title))print(soup.title.string)print(soup.head)print(soup.p) 依次查找 title、head、p 节点。输出结果:12345678<title>测试bs4</title><class 'bs4.element.Tag'>测试bs4<head><meta charset=\"utf-8\"/><title>测试bs4</title></head><p>百里守约</p> 【8.3.2】提取信息 string 属性:获取节点包含的文本值(如果标签里面还有标签,那么string获取到的结果为None) text 属性:获取节点包含的文本值 get_text() 属性:获取节点包含的文本值 name 属性:获取节点的名称 attrs :获取所有属性 attrs[‘属性名’] :获取指定属性 依然以 soup.html 为例:1234567891011121314from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.title)print(soup.title.text)print(soup.title.get_text())print(soup.title.string)print(soup.div.string)print(soup.div.text)print(soup.title.name)print(soup.a['href']) # 获取href属性print(soup.a['title']) # 获取title属性print(soup.a['target']) # 获取target属性print(soup.a.attrs) # 获取所有属性print(soup.a.attrs['href']) # 获取href属性 输出结果:1234567891011121314151617<title>测试bs4</title>测试bs4测试bs4测试bs4None 甄姬 百里守约李白 太乙真人titlehttp://www.song.com/赵匡义_self{'href': 'http://www.song.com/', 'title': '赵匡义', 'target': '_self'}http://www.song.com/ 【8.3.3】嵌套选择12345678910from bs4 import BeautifulSouphtml = \"\"\"<html><head><title>This is a demo</title></head><body>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.head.title)print(type(soup.head.title))print(soup.head.title.string) 获取 head 节点里面的 title 节点,输出结果:123<title>This is a demo</title><class 'bs4.element.Tag'>This is a demo 【8.3.4】关联选择 contents 属性:获取某个节点元素的直接子节点 children 属性:遍历某个节点元素的子节点 descendants 属性:获取某个节点元素所有的子孙节点 parent 属性:获取某个节点元素的父节点 parents 属性:获取某个节点元素所有的祖先节点 next_sibling 属性:获取节点的下一个兄弟元素 previous_sibling 属性:获取节点的上一个兄弟元素 next_siblings 属性:获取某个节点所有后面的兄弟元素 previous_siblings 属性:获取某个节点所有前面的兄弟元素 contents 属性应用示例 12345678910111213141516171819202122from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.contents) 获取 p 节点元素的直接子节点,输出结果:123['\\n Once upon a time there were three little sisters; and their names were\\n ', <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>, '\\n', <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>, ' \\n and\\n ', <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>, '\\n and they lived at the bottom of a well.\\n '] children 属性应用示例: 123456789101112131415161718192021222324from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.children)for i, child in enumerate(soup.p.children): print(i, child) 遍历 p 节点元素的子节点,输出结果:12345678910111213141516<list_iterator object at 0x00000228E3C205F8>0 Once upon a time there were three little sisters; and their names were 1 <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>2 3 <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>4 and 5 <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>6 and they lived at the bottom of a well. descendants 属性应用示例:123456789101112131415161718192021222324from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> <p class=\"story\">...</p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.p.descendants)for i, child in enumerate(soup.p.descendants): print(i, child)获取 p 节点元素所有的子孙节点,输出结果:123456789101112131415161718192021222324<generator object descendants at 0x0000018404A4C3B8>0 Once upon a time there were three little sisters; and their names were 1 <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a>2 3 <span>Elsie</span>4 Elsie5 6 7 <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>8 Lacie9 and 10 <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>11 Tillie12 and they lived at the bottom of a well.parent 属性应用示例:123456789101112131415161718from bs4 import BeautifulSouphtml = \"\"\"<html> <head> <title>The Dormouse's story</title> </head> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> </p> </body></html>\"\"\"soup = BeautifulSoup(html, 'lxml')print(soup.a.parent)获取 a 节点元素的父节点,输出结果:123456<p class=\"story\"> Once upon a time there were three little sisters; and their names were <a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p>parents 属性应用示例:1234567891011121314from bs4 import BeautifulSouphtml = \"\"\"<html> <body> <p class=\"story\"> <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> </p>\"\"\"soup = BeautifulSoup(html, 'lxml')print(type(soup.a.parents))print(list(enumerate(soup.a.parents)))获取 a 节点元素所有的祖先节点,输出结果:1234567891011121314151617181920212223242526<class 'generator'>[(0, <p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p>), (1, <body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body>), (2, <html><body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body></html>), (3, <html><body><p class=\"story\"><a class=\"sister\" href=\"http://example.com/elsie\" id=\"link1\"><span>Elsie</span></a></p></body></html>)]next_sibling、previous_sibling、next_siblings、previous_siblings 属性应用示例: 1234567891011121314151617181920212223html = \"\"\"<html> <body> <p class=\"story\"> Once upon a time there were three little sisters; and their names were <a href=\"http://example.com/elsie\" class=\"sister\" id=\"link1\"> <span>Elsie</span> </a> Hello <a href=\"http://example.com/lacie\" class=\"sister\" id=\"link2\">Lacie</a> and <a href=\"http://example.com/tillie\" class=\"sister\" id=\"link3\">Tillie</a> and they lived at the bottom of a well. </p> </body></html>\"\"\"from bs4 import BeautifulSoupsoup = BeautifulSoup(html, 'lxml')print('Next Sibling', soup.a.next_sibling)print('Prev Sibling', soup.a.previous_sibling)print('Next Siblings', list(enumerate(soup.a.next_siblings)))print('Prev Siblings', list(enumerate(soup.a.previous_siblings))) next_sibling 和 previous_sibling 分别获取 a 节点的下一个和上一个兄弟元素,next_siblings 和 previous_siblings 则分别返回 a 节点后面和前面的兄弟节点,输出结果: 12345678Next Sibling Hello Prev Sibling Once upon a time there were three little sisters; and their names were Next Siblings [(0, '\\n Hello\\n '), (1, <a class=\"sister\" href=\"http://example.com/lacie\" id=\"link2\">Lacie</a>), (2, ' \\n and\\n '), (3, <a class=\"sister\" href=\"http://example.com/tillie\" id=\"link3\">Tillie</a>), (4, '\\n and they lived at the bottom of a well.\\n ')]Prev Siblings [(0, '\\n Once upon a time there were three little sisters; and their names were\\n ')] 【8.4】方法选择器节点选择器直接调用节点的名称就可以选择节点元素,如果进行比较复杂的选择的话,方法选择器是一个不错的选择,它更灵活,常见的方法有 find_all、find 等,调用它们,直接传入相应的参数,就可以灵活查询了。 【8.4.1】find_all() 方法find_all 方法可以查询所有符合条件的元素,给它传入一些属性或文本来得到符合条件的元素。find_all 方法的 API:find_all(name , attrs , recursive , text , **kwargs)新建 soup.html:123456789101112131415161718192021222324252627282930313233343536<!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>测试bs4</title></head><body><div> 甄姬 <p>百里守约</p> <p>李白</p> 太乙真人</div><div class=\"song\"> <p>李清照</p> <p>王安石</p> <p>苏轼</p> <p>柳宗元</p> <a href=\"http://www.song.com/\" title=\"赵匡义\" target=\"_self\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a> <img src=\"http://www.baidu.com/meinv.jpg\" alt=\"\"> <a href=\"\" class=\"du\">总为浮云能蔽日,长安不见使人愁</a></div><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><b>唐朝</b></li> <li><i>宋朝</i></li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 示例代码:12345678910from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.find_all('a'), '\\n')print(soup.find_all('a')[1], '\\n')print(soup.find_all('a')[1].text, '\\n')print(soup.find_all(['a', 'b', 'i']), '\\n')print(soup.find_all('a', limit=2), '\\n')print(soup.find_all(title='qing'), '\\n')print(soup.find_all(attrs={'id': 'feng'}), '\\n') 输出结果:12345678910111213[<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>, <a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class=\"du\" href=\"http://www.sina.com\">杜甫</a>, <a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a> 总为浮云能蔽日,长安不见使人愁 [<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>, <a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class=\"du\" href=\"http://www.sina.com\">杜甫</a>, <b>唐朝</b>, <i>宋朝</i>, <a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] [<a href=\"http://www.song.com/\" target=\"_self\" title=\"赵匡义\">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class=\"du\" href=\"\">总为浮云能蔽日,长安不见使人愁</a>] [<a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>] [<a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] 【8.4.2】find() 方法find() 方法使用方法与 find_all() 方法相同,不同的是,find 方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 返回的是所有匹配的元素组成的列表特别的: find_parents 和 find_parent:前者返回所有祖先节点,后者返回直接父节点。 find_next_siblings 和 find_next_sibling:前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。 find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。 find_all_next 和 find_next:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。 find_all_previous 和 find_previous:前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点。 【8.5】CSS 选择器使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可新建 soup.html 文件:1234567891011121314<!DOCTYPE html><html lang=\"en\"><div class=\"tang\"> <ul> <li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li> <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li> <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li> <li><a href=\"http://www.sina.com\" class=\"du\">杜甫</a> </li> <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li> </ul></div></body></html> 通过 CSS 选择器依次选择 class=”tang” 的 div 节点下的 a 节点、id 为 feng 的节点以及其 href 元素:1234567from bs4 import BeautifulSoupsoup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')print(soup.select('li'), '\\n')print(soup.select('.tang > ul > li > a')[2], '\\n')print(soup.select('#feng')[0].text, '\\n')print(soup.select('#feng')[0]['href'], '\\n') 输出结果:1234567[<li><a href=\"http://www.baidu.com\" title=\"qing\">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>, <li><a href=\"http://www.163.com\" title=\"qin\">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>, <li><a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>, <li><a class=\"du\" href=\"http://www.sina.com\">杜甫</a> </li>, <li><a href=\"http://www.haha.com\" id=\"feng\">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>] <a href=\"http://www.126.com\" name=\"qi\">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> 凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。 http://www.haha.com 附表:CSS 选择器,来源:https://www.w3school.com.cn/cssref/css_selectors.asp 选择器 例子 例子描述 CSS .class .intro 选择 class=”intro” 的所有元素 1 #id #firstname 选择 id=”firstname” 的所有元素 1 * * 选择所有元素 2 element p 选择所有 元素 1 element,element div,p 选择所有 元素和所有 元素 1 element element div p 选择 元素内部的所有 元素 1 element>element div>p 选择父元素为 元素的所有 元素 2 element+element div+p 选择紧接在 元素之后的所有 元素 2 [attribute] [target] 选择带有 target 属性所有元素 2 [attribute=value] [target=_blank] 选择 target=”_blank” 的所有元素 2 [attribute~=value] [title~=flower] 选择 title 属性包含单词 “flower” 的所有元素 2 [attribute =value] [lang =en] 选择 lang 属性值以 “en” 开头的所有元素 2 :link a:link 选择所有未被访问的链接 1 :visited a:visited 选择所有已被访问的链接 1 :active a:active 选择活动链接 1 :hover a:hover 选择鼠标指针位于其上的链接 1 :focus input:focus 选择获得焦点的 input 元素 2 :first-letter p:first-letter 选择每个 元素的首字母 1 :first-line p:first-line 选择每个 元素的首行 1 :first-child p:first-child 选择属于父元素的第一个子元素的每个 元素 2 :before p:before 在每个 元素的内容之前插入内容 2 :after p:after 在每个 元素的内容之后插入内容 2 :lang(language) p:lang(it) 选择带有以 “it” 开头的 lang 属性值的每个 元素 2 element1~element2 p~ul 选择前面有 元素的每个 元素 3 [attribute^=value] a[src^=”https”] 选择其 src 属性值以 “https” 开头的每个 元素 3 [attribute$=value] a[src$=”.pdf”] 选择其 src 属性以 “.pdf” 结尾的所有 元素 3 [attribute*=value] a[src*=”abc”] 选择其 src 属性中包含 “abc” 子串的每个 元素 3 :first-of-type p:first-of-type 选择属于其父元素的首个 元素的每个 元素 3 :last-of-type p:last-of-type 选择属于其父元素的最后 元素的每个 元素 3 :only-of-type p:only-of-type 选择属于其父元素唯一的 元素的每个 元素 3 :only-child p:only-child 选择属于其父元素的唯一子元素的每个 元素 3 :nth-child(n) p:nth-child(2) 选择属于其父元素的第二个子元素的每个 元素 3 :nth-last-child(n) p:nth-last-child(2) 同上,从最后一个子元素开始计数 3 :nth-of-type(n) p:nth-of-type(2) 选择属于其父元素第二个 元素的每个 元素 3 :nth-last-of-type(n) p:nth-last-of-type(2) 同上,但是从最后一个子元素开始计数 3 :last-child p:last-child 选择属于其父元素最后一个子元素每个 元素 3 :root :root 选择文档的根元素 3 :empty p:empty 选择没有子元素的每个 元素(包括文本节点) 3 :target #news:target 选择当前活动的 #news 元素 3 :enabled input:enabled 选择每个启用的 元素 3 :disabled input:disabled 选择每个禁用的 元素 3 :checked input:checked 选择每个被选中的 元素 3 :not(selector) :not(p) 选择非 元素的每个元素 3 ::selection ::selection 选择被用户选取的元素部分 3 【8.6】附表:Beautiful Soup 库 soup 对象常用属性与方法 基本元素 说明 返回类型 tag soup.a bs4.element.Tag name soup.a.name str attrs soup.a.attrs dict contents 子节点 list children 遍历子节点 list_iterator descendants 遍历所有子孙节点 generator parent 返回父亲标签 bs4.element.Tag parents 上行遍历父辈标签 generator prettify() 添加/n str find_all(name,attr) soup.find_all(‘a’)/([‘a’,‘b’])/(True)/(‘p’,‘course’)/(id=‘link1’)/(string=‘python’) bs4.element.ResultSet find() soup.find(‘a’)/返回第一个a标签 bs4.element.Tag","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Beautiful Soup","slug":"Beautiful-Soup","permalink":"https://www.itrhx.com/tags/Beautiful-Soup/"}]},{"title":"Python3 爬虫学习笔记 C07","slug":"A37-Python3-spider-C07","date":"2019-08-25T11:31:18.872Z","updated":"2019-09-24T12:40:30.940Z","comments":true,"path":"2019/08/25/A37-Python3-spider-C07/","link":"","permalink":"https://www.itrhx.com/2019/08/25/A37-Python3-spider-C07/","excerpt":"Python3 爬虫学习笔记第七章 —— 【解析库 lxml】","text":"Python3 爬虫学习笔记第七章 —— 【解析库 lxml】 【7.1】关于 lxml lxml 是 Python 的一个解析库,支持 HTML 和 XML 的解析,支持 XPath 解析方式,解析效率非常高,使用前需要用命令 pip3 install lxml 安装 lxml 库 【7.2】使用 XPath XPath(XML Path Language)即 XML 路径语言, lxml 解析库使用的正是 XPath 语法,最初是用来搜寻 XML 文档的,是一门在 XML 文档中查找信息的语言,它同样适用于 HTML 文档的搜索 XPath 常用规则 表达式 描述 nodename 选取此节点的所有子节点 / 从当前节点选取直接子节点 // 从当前节点选取子孙节点 . 选取当前节点 .. 选取当前节点的父节点 @ 选取属性 * 通配符,选择所有元素节点与元素名 @* 选取所有属性 [@attrib] 选取具有给定属性的所有元素 [@attrib=’value’] 选取给定属性具有给定值的所有元素 [tag] 选取所有具有指定元素的直接子节点 [tag=’text’] 选取所有具有指定元素并且文本内容是text节点 浏览器插件 XPath Helper,在线验证 XPath,谷歌商店下载地址:https://chrome.google.com/webstore/detail/hgimnogjllphhhkhlmebbmlgjoejdpjl XPath 基本使用方法:首先使用代码 from lxml import etree导入库,然后将 HTML 文档变成一个对象,再调用对象的方法去查找指定的节点,方法有两种:tree = etree.parse() 为本地文件查找,tree = etree.HTML() 为网络文件查找,再使用语句 tree.xpath() 查找指定节点。 【7.3】查找所有节点 新建一个 xpath.html 本地文件,内容如下: 123456789101112131415161718192021222324252627282930313233 <!DOCTYPE html><html lang=\"en\"><head> <meta charset=\"UTF-8\" /> <title>xpath测试</title></head><body><div class=\"song\"> 火药 <b>指南针</b> <b>印刷术</b> 造纸术</div><div class=\"tang\"> <ul> <li class=\"balove\">停车坐爱枫林晚,霜叶红于二月花。</li> <li id=\"hua\">商女不知亡国恨,隔江犹唱后庭花。</li> <li class=\"love\" name=\"yang\">一骑红尘妃子笑,无人知是荔枝来。</li> <li id=\"bei\">葡萄美酒夜光杯,欲饮琵琶马上催。</li> <li><a href=\"http://www.baidu.com/\">百度一下</a> </li> </ul> <ol> <li class=\"balucy\">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li> <li class=\"lily\">咋暖还寒时候,最难将息。</li> <li class=\"lilei\">三杯两盏淡酒。</li> <li>怎敌他晚来风急。</li> <li>雁过也,正伤心,却是旧时相识。</li> <li>爱情三十六计</li> <li>什么是爱情</li> </ol></div></body></html> 查找所有节点:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//*')print(result) 使用 * 代表匹配所有节点,整个 xpath.html 文件中的所有节点都会被获取到,返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等,所有节点都包含在列表中,输出结果如下:1[<Element html at 0x1a836a34508>, <Element head at 0x1a836a344c8>, <Element meta at 0x1a836a345c8>, <Element title at 0x1a836a34608>, <Element body at 0x1a836a34648>, <Element div at 0x1a836a346c8>, <Element b at 0x1a836a34708>, <Element b at 0x1a836a34748>, <Element div at 0x1a836a34788>, <Element ul at 0x1a836a34688>, <Element li at 0x1a836a347c8>, <Element li at 0x1a836a34808>, <Element li at 0x1a836a34848>, <Element li at 0x1a836a34888>, <Element li at 0x1a836a348c8>, <Element a at 0x1a836a34908>, <Element ol at 0x1a836a34948>, <Element li at 0x1a836a34988>, <Element li at 0x1a836a349c8>, <Element li at 0x1a836a34a08>, <Element li at 0x1a836a34a48>, <Element li at 0x1a836a34a88>, <Element li at 0x1a836a34ac8>, <Element li at 0x1a836a34b08>] 【7.4】查找子节点 通过 / 或 // 即可查找元素的子节点或子孙节点: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ul/li')print(result) 选择 ul 节点的所有直接 li 子节点:1[<Element li at 0x2a094d044c8>, <Element li at 0x2a094d045c8>, <Element li at 0x2a094d04608>, <Element li at 0x2a094d04648>, <Element li at 0x2a094d04688>] 【7.5】查找父节点 知道了子节点,也可以用 .. 或者 parent:: 查找其父节点 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/../@class')print(result) 12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/parent::*/@class')print(result) 先查找到 ol 节点,随后获取其父节点以及其 class 属性:1['tang'] 【7.6】属性匹配 有时候 HTML 包含多个相同名的节点,而节点的属性是不一样的,此时可以用 @ 符号进行属性过滤 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//li[@class=\"balucy\"]')print(result) xpath.html 文件中,只有一个 class 为 balucy 的节点:<li class="balucy">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li>,运行以上代码将返回一个该元素:1[<Element li at 0x16e53aa54c8>] 【7.7】文本获取 使用 text() 方法即可提取节点中的文本: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//li[@class=\"balucy\"]/text()')print(result) 输出结果:1['寻寻觅觅冷冷清清,凄凄惨惨戚戚。'] 再次观察 xpath.html 文件中的 <ol></ol>这一部分:123456789<ol> <li class=\"balucy\">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li> <li class=\"lily\">咋暖还寒时候,最难将息。</li> <li class=\"lilei\">三杯两盏淡酒。</li> <li>怎敌他晚来风急。</li> <li>雁过也,正伤心,却是旧时相识。</li> <li>爱情三十六计</li> <li>什么是爱情</li></ol> 如果我们想要提取 <li> 节点里面所有的文本,就可以使用 html.xpath('//ol/li/text()') 语句:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol/li/text()')print(result) 输出结果:1['寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '咋暖还寒时候,最难将息。', '三杯两盏淡酒。', '怎敌他晚来风急。', '雁过也,正伤心,却是旧时相识。', '爱情三十六计', '什么是爱情'] 同样还有另一种方法,使用 html.xpath('//ol//text()') 语句,// 将会选取所有子孙节点的文本,<ol> 和 <li> 节点下的换行符也将被提取出来:12345from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ol//text()')print(result) 输出结果:1['\\n ', '寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '\\n ', '咋暖还寒时候,最难将息。', '\\n ', '三杯两盏淡酒。', '\\n ', '怎敌他晚来风急。', '\\n ', '雁过也,正伤心,却是旧时相识。', '\\n ', '爱情三十六计', '\\n ', '什么是爱情', '\\n '] 【7.8】属性获取 与属性匹配一样,属性获取仍然使用 @: 12345 from lxml import etreehtml = etree.parse('./xpath.html')result = html.xpath('//ul/li[5]/a/@href')print(result) 获取 href 属性: 1['http://www.baidu.com/'] 【7.9】一个属性包含多个值的匹配某个节点的某个属性可能有多个值,例如:1<li class=\"li li-first\"><a href=\"link.html\">first item</a></li> li 节点的 class 属性有 li 和 li-first 两个值,如果使用 html.xpath('//li[@class="li"] 语句,将无法成功匹配,这时就需要使用 contains 方法了,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了 1234567from lxml import etreetext = ''' <li class=\"li li-first\"><a href=\"link.html\">first item</a></li> '''html = etree.HTML(text)result = html.xpath('//li[contains(@class, \"li\")]/a/text()')print(result) 输出结果:1['first item'] 【7.10】多个属性匹配一个节点XPath 还可以根据多个属性来确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符 and 来连接:1234567from lxml import etreetext = ''' <li class=\"li\" name=\"item\"><a href=\"link.html\">first item</a></li>'''html = etree.HTML(text)result = html.xpath('//li[@class=\"li\" and @name=\"item\"]/a/text()')print(result) 输出结果:1['first item'] 示例中运用了运算符 and 来连接,此外常见的运算符如下: 运算符 描述 实例 返回值 or 或 age=19 or age=20 如果 age 是 19 或者 20,则返回 true。如果 age 是其他值,则返回 false and 与 age>19 and age<21 如果 age 大于 19 且小于 21,则返回 true。如果 age 是其他值,则返回 false mod 计算除法的余数 5 mod 2 1 | 计算两个节点集 //book | //cd 返回所有拥有 book 和 cd 元素的节点集 + 加法 10 + 5 15 - 减法 10 - 5 5 * 乘法 10 * 5 50 div 除法 10 div 5 2 = 等于 age=19 如果 age 是 19,则返回 true。如果 age 不是 19,则返回 false != 不等于 age!=19 如果 age 不是 19,则返回 true。如果 age 是 19,则返回 false < 小于 age<19 如果 age 小于 19,则返回 true。如果 age 不小于 19,则返回 false <= 小于或等于 age<=19 如果 age 小于等于 19,则返回 true。如果 age 大于 19,则返回 false > 大于 age>19 如果 age 大于 19,则返回 true。如果 age 不大于 19,则返回 false >= 大于或等于 age>=19 如果 age 大于等于 19,则返回 true。如果 age 小于 19,则返回 false 【7.11】按顺序选择节点某些属性可能同时匹配了多个节点,如果要选择其中几个节点,可以利用中括号传入索引的方法获取特定次序的节点12345678910111213141516171819202122from lxml import etreetext = '''<div> <ul> <li class=\"item-0\"><a href=\"link1.html\">first item</a></li> <li class=\"item-1\"><a href=\"link2.html\">second item</a></li> <li class=\"item-inactive\"><a href=\"link3.html\">third item</a></li> <li class=\"item-1\"><a href=\"link4.html\">fourth item</a></li> <li class=\"item-0\"><a href=\"link5.html\">fifth item</a> </ul> </div>'''html = etree.HTML(text)result = html.xpath('//li[1]/a/text()')print(result)result = html.xpath('//li[last()]/a/text()')print(result)result = html.xpath('//li[position()<3]/a/text()')print(result)result = html.xpath('//li[last()-2]/a/text()')print(result) li[1]:选取第一个 li 节点; li[last()]:选取最后一个 li 节点; position()<3:选取位置小于 3 的 li 节点; li[last()-2]:选取倒数第三个 li 节点 输出结果:1234['first item']['fifth item']['first item', 'second item']['third item'] 【7.12】节点轴选择节点轴选择:获取子元素、兄弟元素、父元素、祖先元素等12345678910111213141516171819202122232425262728from lxml import etreetext = '''<div> <ul> <li class=\"item-0\"><a href=\"link1.html\"><span>first item</span></a></li> <li class=\"item-1\"><a href=\"link2.html\">second item</a></li> <li class=\"item-inactive\"><a href=\"link3.html\">third item</a></li> <li class=\"item-1\"><a href=\"link4.html\">fourth item</a></li> <li class=\"item-0\"><a href=\"link5.html\">fifth item</a> </ul> </div>'''html = etree.HTML(text)result = html.xpath('//li[1]/ancestor::*')print(result)result = html.xpath('//li[1]/ancestor::div')print(result)result = html.xpath('//li[1]/attribute::*')print(result)result = html.xpath('//li[1]/child::a[@href=\"link1.html\"]')print(result)result = html.xpath('//li[1]/descendant::span')print(result)result = html.xpath('//li[1]/following::*[2]')print(result)result = html.xpath('//li[1]/following-sibling::*')print(result) 输出结果:1234567[<Element html at 0x1d3749e9548>, <Element body at 0x1d3749e94c8>, <Element div at 0x1d3749e9488>, <Element ul at 0x1d3749e9588>][<Element div at 0x1d3749e9488>]['item-0'][<Element a at 0x1d3749e9588>][<Element span at 0x1d3749e9488>][<Element a at 0x1d3749e9588>][<Element li at 0x1d3749e94c8>, <Element li at 0x1d3749e95c8>, <Element li at 0x1d3749e9608>, <Element li at 0x1d3749e9648>] 基本语法:轴名称::节点测试[谓语] 轴名称对应的结果: 轴名称 结果 ancestor 选取当前节点的所有先辈(父、祖父等) ancestor-or-self 选取当前节点的所有先辈(父、祖父等)以及当前节点本身 attribute 选取当前节点的所有属性 child 选取当前节点的所有子元素 descendant 选取当前节点的所有后代元素(子、孙等) descendant-or-self 选取当前节点的所有后代元素(子、孙等)以及当前节点本身 following 选取文档中当前节点的结束标签之后的所有节点 namespace 选取当前节点的所有命名空间节点 parent 选取当前节点的父节点 preceding 选取文档中当前节点的开始标签之前的所有节点 preceding-sibling 选取当前节点之前的所有同级节点 self 选取当前节点 实例: 例子 结果 child::book 选取所有属于当前节点的子元素的 book 节点 attribute::lang 选取当前节点的 lang 属性 child::* 选取当前节点的所有子元素 attribute::* 选取当前节点的所有属性 child::text() 选取当前节点的所有文本子节点 child::node() 选取当前节点的所有子节点 descendant::book 选取当前节点的所有 book 后代 ancestor::book 选择当前节点的所有 book 先辈 ancestor-or-self::book 选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点) child::*/child::price 选取当前节点的所有 price 孙节点","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"lxml","slug":"lxml","permalink":"https://www.itrhx.com/tags/lxml/"},{"name":"XPath","slug":"XPath","permalink":"https://www.itrhx.com/tags/XPath/"}]},{"title":"Python3 爬虫学习笔记 C06","slug":"A36-Python3-spider-C06","date":"2019-08-24T10:37:05.278Z","updated":"2019-09-24T12:40:18.770Z","comments":true,"path":"2019/08/24/A36-Python3-spider-C06/","link":"","permalink":"https://www.itrhx.com/2019/08/24/A36-Python3-spider-C06/","excerpt":"Python3 爬虫学习笔记第六章 —— 【正则表达式】","text":"Python3 爬虫学习笔记第六章 —— 【正则表达式】 【6.1】关于正则表达式正则表达式是对字符串操作的一种逻辑公式,用定义好的特定字符和这些特定字符的组合组成一个规则字符串,这个规则字符串原来表达对字符串的一种过滤逻辑,从而实现字符串的检索、替换、匹配验证等。Python 的 re 库提供了整个正则表达式的实现,包含五种方法:match、search、findall、sub、compile常用的匹配规则: 模式 描述 \\w 匹配字母、数字及下划线 \\W 匹配不是字母、数字及下划线的字符 \\s 匹配任意空白字符,等价于 [\\t\\n\\r\\f] \\S 匹配任意非空字符 \\d 匹配任意数字,等价于 [0-9] \\D 匹配任意非数字的字符 \\A 匹配字符串开头 \\z 匹配字符串结尾,如果存在换行,同时还会匹配换行符 \\Z 匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串 \\G 匹配最后匹配完成的位置 \\n 匹配一个换行符 \\t 匹配一个制表符 ^ 匹配一行字符串的开头 $ 匹配一行字符串的结尾 . 匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符 […] 用来表示一组字符,单独列出,比如 [amk] 匹配 a、m 或 k [^…] 不在 [] 中的字符,比如 匹配除了 a、b、c 之外的字符 * 匹配 0 个或多个表达式 + 匹配 1 个或多个表达式 ? 匹配 0 个或 1 个前面的正则表达式定义的片段,非贪婪方式 {n} 精确匹配 n 个前面的表达式 {n, m} 匹配 n 到 m 次由前面正则表达式定义的片段,贪婪方式 a\\ b 匹配 a 或 b ( ) 匹配括号内的表达式,也表示一个组 【6.2】re.match 方法match() 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None,在 match() 方法中,第一个参数传入正则表达式,第二个参数传入要匹配的字符串。12345678import recontent = 'This is a Demo_123 4567_I Love China'print(len(content))result = re.match('^This\\s\\w\\w\\s\\w\\s\\w{5}\\d{3}\\s\\w{6}', content)print(result)print(result.group())print(result.span()) 输出结果:123436<_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>This is a Demo_123 4567_I(0, 25) 打印 result 结果是 SRE_Match 对象,表明匹配成功。SRE_Match 对象有两种方法:group() 方法可以输出匹配到的内容;span() 方法可以输出匹配的范围。 【6.2.1】提取内容使用括号将想提取的子字符串括起来。括号实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应每一个分组,调用 group() 方法传入分组的索引即可获取提取的结果。12345678import recontent = 'This is a Demo_123 4567_I Love China'result = re.match('^This\\s\\w\\w\\s\\w\\s(\\w{5})\\d{3}\\s\\w{6}', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>This is a Demo_123 4567_IDemo_(0, 25) 【6.2.2】通用匹配如果每个字符都用都用一个符号来匹配的话就显得比较麻烦,可以用 .*来匹配,. 可以匹配除换行符外的任意字符,* 代表匹配前面的字符无限次。1234567import recontent = 'This is a Demo_123 4567_I Love China'result = re.match('^This.*China$', content)print(result)print(result.group())print(result.span()) 输出结果:123<_sre.SRE_Match object; span=(0, 36), match='This is a Demo_123 4567_I Love China'>This is a Demo_123 4567_I Love China(0, 36) 【6.2.3】贪婪匹配12345678import recontent = 'This is a Demo_1234567_I Love China'result = re.match('^This.*(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>This is a Demo_1234567_I Love China7(0, 35) .* 为贪婪匹配,会匹配尽可能多的字符,所以 \\d+ 只会匹配到最后一个数字,而不是所有的数字 【6.2.4】非贪婪匹配12345678import recontent = 'This is a Demo_1234567_I Love China'result = re.match('^This.*?(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:1234<_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>This is a Demo_1234567_I Love China1234567(0, 35) .*? 为非贪婪匹配,会匹配尽可能少的字符,所以 \\d+ 会匹配到所有的数字 【6.2.5】转义匹配当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。例如 . 可以用 \\. 来匹配:123456import recontent = '(博客)www.itrhx.com'result = re.match('\\(博客\\)www\\.itrhx\\.com', content)print(result)print(result.group()) 输出结果:12<_sre.SRE_Match object; span=(0, 17), match='(博客)www.itrhx.com'>(博客)www.itrhx.com 【6.2.6】修饰符修饰符用来解决换行、大小写等问题,较为常用的有 re.S 和 re.I。 修饰符 描述 re.S 使 . 匹配包括换行在内的所有字符 re.I 使匹配对大小写不敏感 re.L 做本地化识别(locale-aware)匹配 re.M 多行匹配,影响 ^ 和 $ re.U 根据 Unicode 字符集解析字符。这个标志影响 \\w、\\W、\\b 和 \\B re.X 该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解 示例:123456789import recontent = '''This is a Demo_1234567 _I Love China'''result = re.match('^This.*?(\\d+).*China$', content)print(result)print(result.group())print(result.group(1))print(result.span()) 示例中 content 字段进行了换行处理,如果没有修饰符,就会报错:12345Traceback (most recent call last):None File \"F:/PycharmProjects/Python3爬虫/test.py\", line 7, in <module> print(result.group())AttributeError: 'NoneType' object has no attribute 'group' 添加 re.S 修饰符后即可匹配成功:123456789import recontent = '''This is a Demo_1234567 _I Love China'''result = re.match('^This.*?(\\d+).*China$', content, re.S)print(result)print(result.group())print(result.group(1))print(result.span()) 输出结果:12345<_sre.SRE_Match object; span=(0, 46), match='This is a Demo_1234567\\n _I Love China'>This is a Demo_1234567 _I Love China1234567(0, 46) 【6.3】re.search 方法match() 方法只能从字符串的开头开始匹配,一旦开头不匹配,那么整个匹配就失败了,match() 方法更适合用来检测某个字符串是否符合某个正则表达式的规则,而 search() 方法则会扫描整个字符串并返回第一个成功的匹配123456import recontent = 'This is a Demo_1234567_I Love China'result = re.search('a.*?(\\d{5})', content)print(result)print(result.group(1)) 输出结果:12<_sre.SRE_Match object; span=(8, 20), match='a Demo_12345'>12345 【6.4】re.findall 方法search() 方法则会扫描整个字符串,但是返回的是第一个成功的匹配,而 findall() 方法将会返回所有成功的匹配12345678910111213141516171819202122232425262728import rehtml = '''<div id=\"songs-list\"> <h2 class=\"title\"> 民谣 </h2> <p class=\"introduction\"> 民谣歌曲列表 </p> <ul id=\"list\" class=\"list-group\"> <li data-view=\"2\"> 七里香 </li> <li data-view=\"7\"> <a href=\"/2.mp3\" singer=\"赵雷\"> 理想 </a> </li> <li data-view=\"4\" class=\"active\"> <a href=\"/3.mp3\" singer=\"许巍\"> 像风一样自由 </a> </li> <li data-view=\"6\"><a href=\"/4.mp3\" singer=\"安与骑兵\"> 红山果 </a></li> <li data-view=\"5\"><a href=\"/5.mp3\" singer=\"薛之谦\"> 意外 </a></li> <li data-view=\"5\"> <a href=\"/6.mp3\" singer=\"马頔\"> 但南山南 </a> </li> </ul> </div>'''results = re.findall('<li.*?href=\"(.*?)\".*?singer=\"(.*?)\">(.*?)</a>', html, re.S)print(results)print(type(results))for result in results: print(result) print(result[0], result[1], result[2]) 输出结果:123456789101112[('/2.mp3', '赵雷', ' 理想 '), ('/3.mp3', '许巍', ' 像风一样自由 '), ('/4.mp3', '安与骑兵', ' 红山果 '), ('/5.mp3', '薛之谦', ' 意外 '), ('/6.mp3', '马頔', ' 但南山南 ')]<class 'list'>('/2.mp3', '赵雷', ' 理想 ')/2.mp3 赵雷 理想 ('/3.mp3', '许巍', ' 像风一样自由 ')/3.mp3 许巍 像风一样自由 ('/4.mp3', '安与骑兵', ' 红山果 ')/4.mp3 安与骑兵 红山果 ('/5.mp3', '薛之谦', ' 意外 ')/5.mp3 薛之谦 意外 ('/6.mp3', '马頔', ' 但南山南 ')/6.mp3 马頔 但南山南 【6.5】re.sub 方法与字符串的 replace() 方法类似,sub() 方法可以对文本进行修改,sub() 方法第一个参数为匹配对象,第二个参数为替换成的字符串,如果要去掉匹配对象的话,可以赋值为空,第三个参数为原来的字符串12345import recontent = '87dsf4as2w4jh1k4kdl4'result = re.sub('\\d+', '', content)print(result) 输出结果:1dsfaswjhkkdl 【6.5】re.compile() 方法compile() 方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用123456789101112import recontent1 = '北京时间:2019-08-24 18:30'content2 = '伦敦时间:2019-08-24 11:30'content3 = '巴黎时间:2019-08-24 12:30'content4 = '外星时间:9019-99-66 50:30'pattern = re.compile('\\d{2}:\\d{2}')result1 = re.sub(pattern, '', content1)result2 = re.sub(pattern, '', content2)result3 = re.sub(pattern, '', content3)result4 = re.sub(pattern, '', content4)print(result1, result2, result3, result4) 利用 compile() 方法将正则表达式编译成一个正则表达式对象,以便复用,然后用 sub() 方法去掉具体时间输出结果:1北京时间:2019-08-24 伦敦时间:2019-08-24 巴黎时间:2019-08-24 外星时间:9019-99-66","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"正则表达式","slug":"正则表达式","permalink":"https://www.itrhx.com/tags/正则表达式/"}]},{"title":"Python3 爬虫学习笔记 C05","slug":"A35-Python3-spider-C05","date":"2019-08-23T12:13:55.085Z","updated":"2019-09-24T12:40:15.920Z","comments":true,"path":"2019/08/23/A35-Python3-spider-C05/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A35-Python3-spider-C05/","excerpt":"Python3 爬虫学习笔记第五章 —— 【Selenium + 无界面浏览器】","text":"Python3 爬虫学习笔记第五章 —— 【Selenium + 无界面浏览器】 【5.1】关于无界面浏览器无界面(headless)浏览器,会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器更高效。Selenium 搭配无界面浏览器使用,被称为爬虫利器,常用的无界面浏览器有:PhantomJS、Headless Chrome、Headless Firefox,其中,18年3月,PhantomJS 的作者在 GitHub 上宣布暂停开发 PhantomJS,现在使用 PhantomJS 会出现警告:UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead,所以推荐使用谷歌或者火狐的无界面浏览器 【5.2】PhantomJS下载 PhantomJS:https://phantomjs.org/download.htmlpath 为 PhantomJS 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\phantomjs-2.1.1\\bin\\phantomjs.exe'driver = webdriver.PhantomJS(executable_path=path)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close() 【5.3】Headless Chrome下载 Chromedriver:http://chromedriver.storage.googleapis.com/index.html需要本地有 Chrome 浏览器,path 为 Headless Chrome 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数1234567891011from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionschrome_options = Options()chrome_options.add_argument('--headless')chrome_options.add_argument('--disable-gpu')path = 'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'driver = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close() 【5.4】Headless Firefox下载 geckodriver:https://github.com/mozilla/geckodriver/releases/需要本地有 Firefox 浏览器,path 为 Headless Firefox 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数12345678910from selenium.webdriver import Firefoxfrom selenium.webdriver.firefox.options import Optionsoptions = Options()options.add_argument('-headless')path = 'F:\\PycharmProjects\\Python3爬虫\\geckodriver.exe'driver = Firefox(executable_path=path, firefox_options=options)driver.get(\"https://www.itrhx.com\")print(driver.page_source)driver.close()","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"},{"name":"无界面浏览器","slug":"无界面浏览器","permalink":"https://www.itrhx.com/tags/无界面浏览器/"}]},{"title":"常见 User-Agent 大全","slug":"A34-UserAgent","date":"2019-08-23T01:28:22.624Z","updated":"2019-09-24T12:47:21.096Z","comments":true,"path":"2019/08/23/A34-UserAgent/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A34-UserAgent/","excerpt":"","text":"User Agent 中文名为用户代理,简称 UA,是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。Python 爬虫通过伪装 UA 可以绕过某些检测。 以下为搜集的常见的各浏览器的 User-Agent,其中: 安卓操作系统:Android 7.1.1;OPPO R9sk Build/NMF26F PC操作系统:Windows 10 64位 10.0.18362.10000 其他操作系统:iOS、Backerry、WebOS、Symbian、Windows Phone 相关链接: 手机User-Agent大全:http://www.fynas.com/ua User-Agent在线检测:http://www.user-agent.cn/ 常用User-Agent大全:http://www.jsons.cn/useragent/ Windows10 Windows10 / Chrome 75.0.3770.142Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36 Windows10 / Firefox 69.0b15Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0 Windows10 / Opera 63.0.3368.43Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 OPR/63.0.3368.43 Windows10 / Edge 44.18362.1.0User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362 Windows10 / IE 11.10000.18362.0User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; LCTE; rv:11.0) like Gecko Windows10 x64 / Safari 5.1.4(7534.54.16)Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16 Windows10 / QQ浏览器 10.5(3739)Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3722.400 QQBrowser/10.5.3739.400 Windows10 / 360安全浏览器 10.0.1977.0Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE Windows10 / 360极速浏览器 11.0.2179.0Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE Windows10 / UC浏览器 6.2.3964.2Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36 Windows10 / 搜狗浏览器 8.5.10.31270Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0 Windows10 / 猎豹浏览器 6.5.115.19331.8001Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER Windows10 / 傲游浏览器 5.2.7.5000Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36 Windows10 / 2345加速浏览器 10.1.0.19399Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36 Android Android / Chrome 76.0.3809.111Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36 Android / Firefox 68.0.2Mozilla/5.0 (Android 7.1.1; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0 Android / Opera 53.0.2569.141117Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 OPR/53.0.2569.141117 Android / Edge 42.0.2.3819Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.2.3819 Android / QQ浏览器 9.6.1.5190Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.6 Mobile Safari/537.36 Android / OPPO浏览器 10.5.1.2_2c91537Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 OppoBrowser/10.5.1.2 Android / 360浏览器 8.2.0.162Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.97 Mobile Safari/537.36 Android / 360极速浏览器 1.0.100.1078Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 360 Alitephone Browser (1.5.0.90/1.0.100.1078) mso_sdk(1.0.0) Android / UC浏览器 12.6.0.1040Mozilla/5.0 (Linux; U; Android 7.1.1; zh-CN; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.6.0.1040 Mobile Safari/537.36 Android / 猎豹浏览器 5.12.3Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 LieBaoFast/5.12.3 Android / 百度浏览器 7.19Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.116 Mobile Safari/537.36 T7/9.1 baidubrowser/7.19.13.0 (Baidu; P1 7.1.1) Android / 搜狗浏览器 5.22.8.71677Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.106 Mobile Safari/537.36 AWP/2.0 SogouMSE,SogouMobileBrowser/5.22.8 Android / 2345浏览器 11.0.1Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Mb2345Browser/11.0.1 其他 iPhone3Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/1A542a Safari/419.3 iPhone4Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7 iPhone6sMozilla/5.0 (iPhone 6s; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0 MQQBrowser/8.3.0 Mobile/15B87 Safari/604.1 MttCustomUA/2 QBWebViewType/1 WKType/1 iPadMozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10 iPodMozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5 BlackBerryMozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+ WebOS HP TouchpadMozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0 Nokia N97Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124 Windows Phone MangoMozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"User-Agent","slug":"User-Agent","permalink":"https://www.itrhx.com/tags/User-Agent/"}]},{"title":"Selenium 显式等待条件及其含义","slug":"A33-selenium","date":"2019-08-23T01:28:22.478Z","updated":"2019-09-24T12:47:18.475Z","comments":true,"path":"2019/08/23/A33-selenium/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A33-selenium/","excerpt":"","text":"等待条件 含义 title_is 标题是某内容 title_contains 标题包含某内容 presence_of_element_located 节点加载出,传入定位元组,如 (By.ID, ‘p’) visibility_of_element_located 节点可见,传入定位元组 visibility_of 可见,传入节点对象 presence_of_all_elements_located 所有节点加载出 text_to_be_present_in_element 某个节点文本包含某文字 text_to_be_present_in_element_value 某个节点值包含某文字 frame_to_be_available_and_switch_to_it frame 加载并切换 invisibility_of_element_located 节点不可见 element_to_be_clickable 节点可点击 staleness_of 判断一个节点是否仍在 DOM,可判断页面是否已经刷新 element_to_be_selected 节点可选择,传节点对象 element_located_to_be_selected 节点可选择,传入定位元组 element_selection_state_to_be 传入节点对象以及状态,相等返回 True,否则返回 False element_located_selection_state_to_be 传入定位元组以及状态,相等返回 True,否则返回 False alert_is_present 是否出现 Alert 更多等待条件极其用法介绍:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions Selenium 的使用:https://www.itrhx.com/2019/08/22/A32-Python3-spider-C04/","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"}]},{"title":"Python3 爬虫学习笔记 C04","slug":"A32-Python3-spider-C04","date":"2019-08-23T01:28:22.327Z","updated":"2019-09-24T12:43:57.196Z","comments":true,"path":"2019/08/23/A32-Python3-spider-C04/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A32-Python3-spider-C04/","excerpt":"Python3 爬虫学习笔记第四章 —— 【自动化测试工具 Selenium】","text":"Python3 爬虫学习笔记第四章 —— 【自动化测试工具 Selenium】 Selenium 是一个用于 Web 应用程序测试的工具。Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些 JavaScript 动态渲染的页面来说,此种抓取方式非常有效。本文重点以 Selenium 使用谷歌浏览器的 Webdriver 为例。 【4.1】下载驱动使用 Selenium 操作不同浏览器,需要不同浏览器相应的驱动支持: 浏览器 驱动名称 下载地址 备注 谷歌浏览器 chromedriver 点击进入下载页面 需要根据自己浏览器的版本下载不同版本的驱动 火狐浏览器 geckodriver 点击进入下载页面 需要根据自己的操作系统下载对应的驱动 IE IEDriverServer 点击进入下载页面 根据自己 selenium 版本和系统版本下载对应版本的驱动, selenium 版本可以在cmd中输入pip show selenium查看 【4.2】声明浏览器对象不同浏览器的对象声明方法:1234567from selenium import webdriverbrowser = webdriver.Chrome() # 谷歌浏览器browser = webdriver.Firefox() # 火狐浏览器browser = webdriver.Edge() # Edgebrowser = webdriver.PhantomJS() # PhantomJS无界面浏览器browser = webdriver.Safari() # Safari浏览器 【4.3】访问页面1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')print(browser.page_source)browser.close() 运行代码就会自动打开谷歌浏览器,实现了用 get() 方法访问 www.itrhx.com ,path 里面的内容是谷歌浏览器驱动的目录, r 表示不转义,使用真实字符。print(browser.page_source) 表示打印页面源代码 【4.4】启动参数Chrome Options 是一个 Chrome 的参数对象,在此对象中使用 add_argument() 方法可以添加启动参数,添加完毕后可以在初始化 Webdriver 对象时将此 Options 对象传入,则可以实现以特定参数启动Chrome。示例:123456789101112from selenium import webdriverfrom selenium.webdriver.chrome.options import Optionspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'# 实例化一个启动参数对象chrome_options = Options()# 添加启动参数chrome_options.add_argument('--window-size=1366,768')# 将参数对象传入Chrome,则启动了一个设置了窗口大小的Chromebrowser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)browser.get('http://www.itrhx.com') 这样就启动了一个1366x768分辨率的浏览器常见的启动参数: 启动参数 作用 –user-agent=”” 设置请求头的 User-Agent –window-size=xxx, xxx 设置浏览器分辨率 –headless 无界面运行 –start-maximized 最大化运行 –incognito 隐身模式 –disable-javascript 禁用javascript –disable-infobars 禁用“浏览器正在被自动化程序控制”的提示 所有的启动参数:https://peter.sh/experiments/chromium-command-line-switches/ 【4.5】查找节点Selenium 可以驱动浏览器完成各种操作,比如填充表单、模拟点击等。要完成这些操作,实现要知道在哪里点击,哪里填充,这就是 Selenium 节点查找 【4.5.1】查找单个节点所有获取单个节点的方法: find_element_by_id 【通过元素的 id 来选择】例:<div id='bdy-inner'>test</div>,查找:driver.find_element_by_id('bdy-inner') find_element_by_name 【通过元素的 name 来选择】例:<input name="username" type="text" />,查找:driver.find_element_by_name('password') find_element_by_xpath 【通过 xpath 选择】例:<form id="loginForm">,查找:driver.find_element_by_xpath("//form[@id='loginForm']") find_element_by_link_text 【通过链接地址选择】例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('continue') find_element_by_partial_link_text 【通过链接的部分地址选择】例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('cont') find_element_by_tag_name 【通过元素的名称选择】例:<h1>welcome<h1>,查询:driver.find_element_by_tag_name('h1') find_element_by_class_name 【通过元素的 class 选择】例:<p class="content">welcome to TRHX'S BLOG!</p>,查询:driver.find_element_by_class_name('content') find_element_by_css_selector 【通过元素的 class 选择】例:<div class='bdy-inner'>test</div>,查询:driver.find_element_by_css_selector('div.bdy-inner') find_element() 【通用方法,需要传递两个参数:查找方式 By 和值】例:driver.find_element_by_id('inner') 等价于 find_element(By.ID, inner),使用时需要from selenium.webdriver.common.by import By 示例:12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')blog_title = browser.find_elements_by_class_name(('title'))print(blog_title[0].text)browser.close() 输出结果:1TRHX'S BLOG 【4.5.2】查找多个节点所有获取多个节点的方法:(与查找单个节点的区别是 element 多加了个 s) find_elements_by_id find_elements_by_name find_elements_by_xpath find_elements_by_link_text find_elements_by_partial_link_text find_elements_by_tag_name find_elements_by_class_name find_elements_by_css_selector find_elements() 示例:123456789from selenium import webdriverfrom selenium.webdriver.common.by import Bypath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')article_title = browser.find_elements(By.XPATH, \"//h2[@class='title']\")print(article_title)browser.close() 【4.6】节点交互Selenium 可以驱动浏览器来执行一些操作,也就是说可以让浏览器模拟执行一些动作。称为节点交互,比较常见的用法有: send_keys:模拟按键输入 clear:清除元素的内容 click:单击元素 submit:提交表单 示例:123456789from selenium import webdriverfrom selenium.webdriver.common.keys import Keyspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')search = browser.find_element_by_xpath('//div[@class=\"cover-wrapper\"]/cover/div/form/input')search.send_keys(\"Python\")search.send_keys(Keys.ENTER) 此处模拟了键盘,需要导入键盘类 Keys(),send_keys(Keys.ENTER)表示模拟回车键,程序首先打开 www.itrhx.com ,也就是我的博客,然后通过 xpath 找到搜索框,输入 Python 并回车,等待结果显示出来更多节点交互动作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement 【4.7】动作链Selenium 还有另外一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,那就是动作链。以一个拖曳实例为例:http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable12345678910111213from selenium import webdriverfrom selenium.webdriver import ActionChainspath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'browser.get(url)browser.switch_to.frame('iframeResult')source = browser.find_element_by_css_selector('#draggable')target = browser.find_element_by_css_selector('#droppable')actions = ActionChains(browser)actions.drag_and_drop(source, target)actions.perform() 依次选中要拖曳的节点和拖曳到的目标节点,接着声明 ActionChains 对象并将其赋值为 actions 变量,然后通过调用 actions 变量的 drag_and_drop() 方法,再调用 perform() 方法执行动作,此时就完成了拖曳操作,更多动作链操作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains 【4.8】执行 JavaScriptSelenium API 并没有提供执行 JavaScript 的方法,但是实际上是可以实现的。比如,下拉进度条,它可以直接模拟运行 JavaScript,此时使用 execute_script() 方法即可实现示例:1234567from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')browser.execute_script('alert(\"已到达最底端!\")') 以上代码实现了利用 execute_script() 方法将进度条下拉到最底部,然后弹出 alert 提示框。 【4.9】禁用加载使用Selenium 时,限制图片和 Javascript 执行,从而提高网页加载速度。123456789101112131415from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'options = webdriver.ChromeOptions()prefs = { 'profile.default_content_setting_values': { 'images': 2, 'notifications' : 2, # 禁用弹窗 'javascript': 2 # 2即为禁用的意思 }}options.add_experimental_option('prefs', prefs)browser = webdriver.Chrome(executable_path=path, chrome_options=options)browser.get('http://www.itrhx.com') 【4.10】获取节点信息通过 page_source 属性可以获取网页的源代码,然后可以使用解析库(如正则表达式、Beautiful Soup等)来提取相关信息,Selenium 已经提供了选择节点的方法,返回的是 WebElement 类型,它也有相关的方法和属性来直接提取节点信息,如属性、文本等。就不需要再次使用解析库来提取信息了 【4.10.1】获取属性使用 get_attribute() 方法来获取节点的属性:123456789from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)meta = browser.find_element_by_id('header-meta')print(meta)print(meta.get_attribute('class')) 输出结果:12<selenium.webdriver.remote.webelement.WebElement (session=\"d03cdaa497441d2e2a5161139b4a7ea5\", element=\"83f8fff9-60d7-4e9a-ade3-a8e97c9f0844\")>meta 【4.10.2】获取文本值每个 WebElement 节点都有 text 属性,直接调用这个属性就可以得到节点内部的文本信息,相当于 Beautiful Soup 的 get_text() 方法、pyquery 的 text() 方法示例:12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)footer_info = browser.find_element_by_id('footer')print(footer_info.text) 输出结果:123Copyright 2018-2019 TRHX'BLOG | 鄂ICP备19003281号-4 | 本站已勉强存活了 376 天 20 小时 57 分 52 秒 | 站点地图 | 站长统计PoweredHexo HostedGitHub DNRAliyun CDNjsDelivr ThemeMaterial X BY-NC-SA 4.0 Link996.ICU UV4898 PV22066 WordCount54.9k 【4.10.3】获取 ID、位置、标签名、大小其他属性,比如 id 属性可以获取节点 id,location 属性可以获取该节点在页面中的相对位置,tag_name 属性可以获取标签名称,size 属性可以获取节点的大小等示例:1234567891011from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)url = 'http://www.itrhx.com'browser.get(url)readmore = browser.find_element_by_class_name('readmore')print(readmore.id)print(readmore.location)print(readmore.tag_name)print(readmore.size) 输出结果:12347df561d3-7ea4-4b90-96aa-64044060bb47{'x': 50, 'y': 1063}div{'height': 39, 'width': 465} 【4.11】延时等待在 Selenium 中,get() 方法会在网页框架加载结束后结束执行,某些页面有额外的 Ajax 请求,若此时立即获取 page_source,可能并不是浏览器完全加载完成的页面,这里需要延时等待一定时间,确保节点已经加载出来 【4.11.1】隐式等待当查找节点的时候,节点并没有立即出现,隐式等待将等待一段时间再查找该节点,使用 implicitly_wait() 方法可以实现隐式等待12345678from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.implicitly_wait(10)browser.get('https://www.itrhx.com')readmore = browser.find_element_by_class_name('readmore')print(readmore) 【4.11.2】显式等待指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就立即返回查找的节点,果到了规定时间依然没有加载出该节点,则抛出超时异常123456789101112from selenium import webdriverfrom selenium.webdriver.common.by import Byfrom selenium.webdriver.support.ui import WebDriverWaitfrom selenium.webdriver.support import expected_conditions as ECpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.implicitly_wait(10)browser.get('https://www.itrhx.com')wait = WebDriverWait(browser, 10)footer_info = wait.until(EC.presence_of_element_located((By.ID, 'footer')))print(footer_info) 引入 WebDriverWait 对象,指定最长等待时间,调用它的 until() 方法,传入要等待条件 expected_conditions。比如,这里传入了 presence_of_element_located 这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是 ID 为 footer 的节点。 这样可以做到的效果就是,在 10 秒内如果 ID 为 footer 的节点成功加载出来,就返回该节点;如果超过 10 秒还没有加载出来,就抛出异常。 加载成功时输出结果:1<selenium.webdriver.remote.webelement.WebElement (session=\"4ca7015891fded627ab680d9462e9361\", element=\"3a80235c-9824-420b-b827-662638422765\")> 加载失败时输出结果:12345TimeoutException Traceback (most recent call last)<ipython-input-4-f3d73973b223> in <module>() 7 browser.get('https://www.itrhx.com') 8 wait = WebDriverWait(browser, 10)----> 9 input = wait.until(EC.presence_of_element_located((By.ID, 'footer'))) 【4.12】Cookies使用 Selenium,可以方便地对 Cookies 进行获取、添加、删除等操作:12345678910from selenium import webdriverpath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.zhihu.com/explore')print(browser.get_cookies())browser.add_cookie({'name': 'TRHX', 'domain': 'www.zhihu.com', 'value': 'germey'})print(browser.get_cookies())browser.delete_all_cookies()print(browser.get_cookies()) 访问知乎,加载完成后,浏览器已经生成了 Cookies。调用 get_cookies() 方法获取所有的 Cookies。然后再添加一个 Cookie,传入一个字典,有 name、domain 和 value 等内容。接下来,再次获取所有的 Cookies。可以发现,结果就多了这一项新加的 Cookie。最后,调用 delete_all_cookies() 方法删除所有的 Cookies。再重新获取,发现结果就为空了输出结果:123[{'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '\"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741\"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}][{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'TRHX', 'path': '/', 'secure': True, 'value': 'germey'}, {'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '\"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741\"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}][{'domain': 'zhihu.com', 'expiry': 1644217741.489889, 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'WNOjpDbNmz36B4nG1lzSAuPdTyORMX6J'}] 【4.13】前进与后退使用 back() 方法后退,使用 forward() 方法前进,与浏览器的前进后退一样示例:123456789101112from selenium import webdriverimport timepath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com/')browser.get('https://www.baidu.com/')browser.get('https://www.zhihu.com/')browser.back()time.sleep(1)browser.forward()browser.close() 【4.14】选项卡和浏览器一样,在 Selenium 中也可以新建一个选项卡12345678910111213from selenium import webdriverimport timepath = r'F:\\PycharmProjects\\Python3爬虫\\chromedriver.exe'browser = webdriver.Chrome(executable_path=path)browser.get('https://www.itrhx.com')browser.execute_script('window.open()')print(browser.window_handles)browser.switch_to.window(browser.window_handles[1])browser.get('https://www.baidu.com')time.sleep(1)browser.switch_to.window(browser.window_handles[0])browser.get('https://www.zhihu.com') 首先访问我的博客,然后调用了 execute_script() 方法,传入 window.open() 这个 JavaScript 语句开启一个新的选项卡。再调用 window_handles 属性获取当前开启的所有选项卡,返回的是选项卡的代号列表。调用 switch_to_window() 方法来切换选项卡,其中参数是选项卡的代号。输出的选项卡代号列表:1['CDwindow-C9CADF1ED28CE44970655238552A8DCF', 'CDwindow-538D7F81E467746B7BB2D9D82E2D036E']","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Selenium","slug":"Selenium","permalink":"https://www.itrhx.com/tags/Selenium/"}]},{"title":"Python3 爬虫学习笔记 C03","slug":"A31-Python3-spider-C03","date":"2019-08-23T01:28:22.178Z","updated":"2019-09-24T12:43:59.900Z","comments":true,"path":"2019/08/23/A31-Python3-spider-C03/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A31-Python3-spider-C03/","excerpt":"Python3 爬虫学习笔记第三章 ——【Ajax 数据爬取】","text":"Python3 爬虫学习笔记第三章 ——【Ajax 数据爬取】 【3.1】Ajax 简介Ajax — Asynchronous Javascript And XML(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。可以在不重新加载整个网页的情况下,对网页的某部分进行更新。 【3.2】解析真实地址提取以豆瓣电影动作片排行榜为例,地址为:https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action= ,首先使用常用方法来爬取电影信息:12345678import requestsurl = 'https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action='headers = {\"User-Agent\": \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0\"}response = requests.get(url, headers=headers)print(response.text) 得到的数据里面我们并没有找到电影相关信息:再次分析页面,发现鼠标下滑的时候,页面不刷新,URL 也不变,但是会加载新数据,那么此处就运用了 Ajax,可以使用抓包工具或者浏览器控制台来捕获 Ajax 接口,获取其真实地址,XHR 是 Ajax 特殊的请求类型,返回的是 json 数据,利用浏览器控制台过滤 XHR,随便点击一条请求,可以看到其 Request URL,也就是真实地址,点击 Preview 就可以看到返回的 json 数据。同样,我们可以使用 Fiddler 抓包软件抓取 Ajax 接口:分析其真实地址为:https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=20&limit=20 ,多下滑几次,只有 start 参数发生了改变,观察变化可知:每一次页面将多出20个电影信息,start 为从第几个电影开始,由此就不难进行数据抓取了 代码:1234567891011121314import requestsurl = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&'page = int(input('请输入想要第几页的数据:'))data = { 'start': (page - 1)*20, 'limit': '20',}headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',}response = requests.get(url, params=data, headers=headers)print(response.text) 运行代码即可得到电影排行信息:","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"Ajax","slug":"Ajax","permalink":"https://www.itrhx.com/tags/Ajax/"}]},{"title":"Python3 爬虫学习笔记 C02","slug":"A30-Python3-spider-C02","date":"2019-08-23T01:28:22.053Z","updated":"2020-03-14T06:19:49.153Z","comments":true,"path":"2019/08/23/A30-Python3-spider-C02/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/","excerpt":"Python3 爬虫学习笔记第二章 ——【基本库 requests 的使用】","text":"Python3 爬虫学习笔记第二章 ——【基本库 requests 的使用】 【2.1】 requests 简介在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库 requests 模块需要使用 pip install 命令安装安装,相比 urllib,它的 API 更加人性化,使用 requests 可以让 Cookies、登录验证、代理设置等操作更加简便,官网介绍:http://cn.python-requests.org 【2.2】 requests 基本用法示例:123456789import requestsr = requests.get('https://www.itrhx.com/')print(type(r))print(r.encoding)print(r.status_code)print(r.cookies)print(r.json)print(r.text)print(r.content) 输出结果:12345678910<class 'requests.models.Response'>utf-8200<RequestsCookieJar[]><bound method Response.json of <Response [200]>><!DOCTYPE html><html><head> <meta charset=\"utf-8\"> ...... r.encoding:服务器内容使用的文本编码; r.status_code:响应状态码,200 代表成功,4xx 代表客户端错误,5xx 服务器响应错误; r.cookies:返回 Cookies; r.json:Requests 内置 JSON 解码器; r.text:服务器响应内容,根据响应头部的字符编码自动解码; r.content:字节方式的响应体,自动解码 gzip 和 deflate 编码的响应。 【2.3】 requests 构建 GET 请求 【2.3.1】 基本用法示例:123456789import requestsdata = { 'name': 'TRHX', 'age': '20'}r = requests.get(\"http://httpbin.org/get\", params=data)print('编码后的URL:', r.url)print('字符串方式的响应体:', r.text) 输出结果:123456789101112131415编码后的URL: http://httpbin.org/get?name=TRHX&age=20字符串方式的响应体: { \"args\": { \"age\": \"20\", \"name\": \"TRHX\" }, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/get?name=TRHX&age=20\"} 【2.3.2】 二进制数据抓取以抓取 GitHub 站点图标为例:12345import requestsr = requests.get(\"https://github.com/favicon.ico\")with open('favicon.ico', 'wb') as f: f.write(r.content) 该代码将会保存站点图标到本地,其他的,比如音频,视频文件都是由二进制码组成的,皆可使用该方法 【2.3.3】 添加 headersheaders 的作用:部分页面禁止 Python 爬虫对其进行爬取,而添加 headers 就可以模拟成浏览器取访问网站,实现数据的爬取,headers 可以在任意网页 F12 检查控制台里面找到,headers 最重要的是 “User-Agent” 字段 以为例知乎,只有加了 headers 才能正常爬取,否则会返回 400 Bad Request 没有任何数据 123456import requestsheaders = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}r = requests.get(\"https://www.zhihu.com/explore\", headers=headers)print(r.text) 【2.4】 requests 构建 POST 请求示例:12345import requestsdata = {'name': 'TRHX', 'age': '20'}r = requests.post(\"http://httpbin.org/post\", data=data)print(r.text) 输出结果: 1234567891011121314151617181920{ \"args\": {}, \"data\": \"\", \"files\": {}, \"form\": { \"age\": \"22\", \"name\": \"germey\" }, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Content-Length\": \"18\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"json\": null, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/post\"} 有关 POST 和 GET 两种请求的一些区别: POST 更加安全,不会作为 URL 的一部分,不会被缓存,保存在服务器日志、以及浏览器浏览记录中; POST 发送的数据更大,GET 有 URL 长度限制; POST 可以发送更多的数据类型,GET 只能发送 ASCII 字符; POST 比 GET 慢; POST 查询参数在 WebForms 保存,GET 查询参数在 QueryString 保存; POST 用数据的修改和写入,GET 一般用于搜索排序和筛选之类的操作。 【2.5】 requests 高级用法 【2.5.1】 上传文件示例: 12345import requestsfiles = {'file': open('test.png', 'rb')}r = requests.post('http://httpbin.org/post', files=files)print(r.text) 输出结果: 12345678910111213141516171819{ \"args\": {}, \"data\": \"\", \"files\": { \"file\": \"data:application/octet-stream;base64,iVBOR......\" }, \"form\": {}, \"headers\": { \"Accept\": \"*/*\", \"Accept-Encoding\": \"gzip, deflate\", \"Content-Length\": \"81383\", \"Content-Type\": \"multipart/form-data; boundary=e36a8686cd77c79dc02bfe9d1b010f08\", \"Host\": \"httpbin.org\", \"User-Agent\": \"python-requests/2.22.0\" }, \"json\": null, \"origin\": \"171.115.102.230, 171.115.102.230\", \"url\": \"https://httpbin.org/post\"} 【2.5.2】 使用 Cookies对于需要登录后才能获取数据的网页,可以将账号登录的 Cookies 添加到 headers 来实现网页登录爬取,Cookies 可以抓包获取,代码示例: 123456789import requestsheaders = { 'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Host': 'www.zhihu.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',}r = requests.get('https://www.zhihu.com', headers=headers)print(r.text) 【2.5.3】 会话维持 背景介绍:利用 get() 或者 post() 方法来模拟网页请求,相当于是不同的会话,可以理解为用两个浏览器打开了不同的网页; 运用场景:首先使用 post() 方法登录网页,然后再使用 get() 方法请求某个页面信息,如果不利用会话维持,将无法获取页面数据 维持方法:①两次请求设置一样的 cookies,缺点:繁琐;②使用 Session 对象。 Session 对象使用示例: 123456 import requestss = requests.Session()s.get('http://httpbin.org/cookies/set/number/123456789')r = s.get('http://httpbin.org/cookies')print(r.text) 输出结果成功获取到设置的 cookies: 12345{ \"cookies\": { \"number\": \"123456789\" }} 【2.5.4】 SSL 证书验证 SSL 证书是数字证书的一种,由受信任的数字证书颁发机构 CA 在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,网站带有 HTTPS 就表明有 SSL 证书 requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,verify 参数可以控制是否检查此证书。如果不加 verify 参数,默认为 True,会自动验证。当一个页面的 SSL 证书没有被官方机构认证时,打开页面就会提示“您的连接不是私密连接”,如果没有设置 verify 参数,将会报以下错误: 1requests.exceptions.SSLError: (\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",) 设置 verify 参数代码示例: 1234import requestsresponse = requests.get('https://www.itrhx.com', verify=False)print(response.text) 【2.5.5】 设置代理为什么要设置代理:某些网页有反爬虫机制,频繁请求网页就会出现验证码等,还有可能直接封掉 IP,导致爬取失败;这种情况下就可以设置 proxies 参数。示例: 12345678import requestsproxies = { 'http': 'http://10.10.1.10:1010', 'https': 'http://10.10.1.10:1020',}requests.get('https://www.itrhx.com', proxies=proxies) 免费代理可在西刺代理找到 【2.5.6】 超时设置与 urllib.request.urlopen() 类似,requests 也可以设置 timeout 参数,请求分为两个阶段:连接和读取 设置连接和读取时间总和: 1234import requestsr = requests.get('https://www.itrhx.com', timeout=1)print(r.status_code) 分别设置连接和读取时间: 1234import requestsr = requests.get('https://www.itrhx.com', timeout=(5, 10))print(r.status_code) 永久等待: 123456import requests# 两种方法实现# r = requests.get('https://www.itrhx.com')r = requests.get('https://www.itrhx.com', timeout=None)print(r.status_code)","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"requests","slug":"requests","permalink":"https://www.itrhx.com/tags/requests/"}]},{"title":"Python3 爬虫学习笔记 C01","slug":"A29-Python3-spider-C01","date":"2019-08-23T01:28:21.841Z","updated":"2019-09-24T12:39:54.253Z","comments":true,"path":"2019/08/23/A29-Python3-spider-C01/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A29-Python3-spider-C01/","excerpt":"Python3 爬虫学习笔记第一章 ——【基本库 urllib 的使用】","text":"Python3 爬虫学习笔记第一章 ——【基本库 urllib 的使用】 【1.1】 urllib 简介在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库 urllib 库:Python 内置的 HTTP 请求库,无需额外安装即可使用;Python 2 中有 urllib 和 urllib2 两个库来实现请求的发送,Python 3 中统一为 urllib。官方文档:https://docs.python.org/3/library/urllib.html urllib 所包含的常用模块: urllib.request:模拟发送请求; urllib.error:异常处理模块,用于捕获异常; urllib.parse:解析、拆分、合并URL; urllib.robotparser:读取网站的 robots.txt 文件,判断哪些内容可以爬取。 urllib.request 所包含的常用方法: urllib.request.urlopen():打开网址URL,这可以是一个字符串或一个 Request对象; urllib.request.Request():在请求的时候传入一些 headers 等信息; urllib.request.urlretrieve():将获取的URL的内容写到文件目录中去。 urllib.error 所包含的两个异常: URLError:继承自 OSError 类,是 error 异常模块的基类,由 request 模块产生的异常都可以通过捕获这个类来处理。 HTTPError:是 URLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。 urllib.parse 所包含的常用方法: urllib.parse.urlencode():将字典参数序列化为 GET 请求参数; urllib.parse.parse_qs():将 GET 请求参数反序列化转回字典; urllib.parse.parse_qsl():将参数转化为元组组成的列表; urllib.parse.urlparse():对 URL 进行分段(返回6个结果); urllib.parse.urlunparse():对 URL 进行组合(长度必须为6); urllib.parse.urlsplit():对 URL 进行分段(不单独解析params部分,返回5个结果); urllib.parse.urlunsplit():对 URL 进行组合(长度必须为5); urllib.parse.urljoin():对 URL 进行组合(没有长度限制,给定两个参数,自动分析 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果); urllib.parse.quote():将内容转化为 URL 编码格式; urllib.parse.unquote():对 URL 进行解码。 urllib.robotparser 所包含的类: RobotFileParser:根据网站的 robots.txt 文件来判断一个爬取爬虫是否有权限来爬取这个网页 【1.2】 urllib.request 发送请求【1.2.1】 urllib.request.urlopen()【1.2.1.1】 基本使用方法urlopen() 函数的 API:1urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None) 基本使用:运行以下代码可得到 https://www.itrhx.com/ 的网页源代码:1234import urllib.requestresponse = urllib.request.urlopen('https://www.itrhx.com/')print(response.read().decode('utf-8')) 输出响应对象的类型和属性:1234567import urllib.requestresponse = urllib.request.urlopen('https://www.itrhx.com/')print(type(response)) # 响应类型print(response.status) # 返回结果的状态码,200代表请求成功print(response.getheaders()) # 响应的头信息print(response.getheader('Server')) # 获取响应头的 server 值 运行结果:1234<class 'http.client.HTTPResponse'>200[('Content-Type', 'text/html; charset=utf-8'), ('Server', 'GitHub.com'), ('Last-Modified', 'Sat, 17 Aug 2019 12:16:48 GMT'), ('ETag', '\"5d57f030-10863\"'), ('Access-Control-Allow-Origin', '*'), ('Expires', 'Sat, 17 Aug 2019 19:41:25 GMT'), ('Cache-Control', 'max-age=600'), ('X-Proxy-Cache', 'MISS'), ('X-GitHub-Request-Id', 'C748:735D:5B7461:619B95:5D58560B'), ('Content-Length', '67683'), ('Accept-Ranges', 'bytes'), ('Date', 'Sun, 18 Aug 2019 13:28:44 GMT'), ('Via', '1.1 varnish'), ('Age', '228'), ('Connection', 'close'), ('X-Served-By', 'cache-tyo19931-TYO'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '1'), ('X-Timer', 'S1566134924.190474,VS0,VE0'), ('Vary', 'Accept-Encoding'), ('X-Fastly-Request-ID', '25a69f8130fc9cae412d28990a724543d7d05e8b')]GitHub.com 【1.2.1.2】 添加参数根据 urlopen() 函数的 API 可知,除了最基本的 URL 参数以外,我们还可以传递其他内容,比如 data(附加数据)、timeout(超时时间)等,以下用 data 和 timeout 参数举例说明。 ● data 参数如果要添加 data 参数,需要使用 bytes 方法将参数转化为字节流编码格式的内容,即 bytes 类型。另外,如果传递了这个参数,则它的请求方式就不再是 GET 方式,而是 POST 方式。代码示例:123456import urllib.parseimport urllib.requestdata = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')response = urllib.request.urlopen('http://httpbin.org/post', data=data)print(response.read()) httpbin.org 站点提供 HTTP 请求测试,http://httpbin.org/post 用于测试 POST 请求,示例中传递一个值为 hello 的 word 参数。使用 bytes 方法,将其转码成 bytes(字节流)类型。该方法的第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode 方法来将参数字典转化为字符串;第二个参数指定编码格式为 utf8,运行结果:123456789101112131415161718b'{ \"args\": {}, \"data\": \"\", \"files\": {}, \"form\": { \"word\": \"hello\" }, \"headers\": { \"Accept-Encoding\": \"identity\", \"Content-Length\": \"10\", \"Content-Type\": \"application/x-www-form-urlencoded\", \"Host\": \"httpbin.org\", \"User-Agent\": \"Python-urllib/3.6\" }, \"json\": null, \"origin\": \"171.115.101.10, 171.115.101.10\", \"url\": \"https://httpbin.org/post\"}' ● timeout 参数举例:1234import urllib.requestresponse = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) print(response.read()) 运行结果:12345678...During handling of the above exception, another exception occurred:Traceback (most recent call last): File \"C:/Users/Lenovo/Desktop/1.py\", line 2, in <module> response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1) ...urllib.error.URLError: <urlopen error timed out> timeout 设置为0.1,0.1秒过后服务器没有响应,便会抛出 URLError 异常进阶:使用 try except 语句抛出异常 【1.2.2】 urllib.request.Request()Request() 方法可以在请求的时候传入一些 data、headers 等信息Request() 的构造方法:1class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None) 构造方法各个参数的解释: url:用于请求 URL,这是必传参数,其他都是可选参数。 data:如果要传,必须传 bytes(字节流)类型的。如果它是字典,可以先用 urllib.parse 模块里的 urlencode() 编码。 headers:是一个字典,它就是请求头,可以在构造请求时通过 headers 参数直接构造,也可以通过调用请求实例的 add_header() 方法添加。添加请求头最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。 origin_req_host:指的是请求方的 host 名称或者 IP 地址。 unverifiable:表示这个请求是否是无法验证的,默认是 False,意思就是说用户没有足够权限来选择接收这个请求的结果。例如,我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。 method:是一个字符串,用来指示请求使用的方法,比如 GET、POST 和 PUT 等。 简单举例:1234567891011121314import urllib.requestimport urllib.parseurl = 'http://www.baidu.com/'# 定制要伪装的头部headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'}# 构建请求对象request = urllib.request.Request(url=url, headers=headers)# 发送请求response = urllib.request.urlopen(request)print(response.read().decode()) 【1.2.3】 urllib.request.urlretrieve()将获取到的 URL 内容保存到当前文件夹,简单举例:123456789import urllib.requesturl = 'https://www.itrhx.com/images/trhx.png'# response = urllib.request.urlopen(image_url)# with open('trhx.png', 'wb') as fp:# fp.write(response.read())urllib.request.urlretrieve(url, 'trhx.png') 【1.3】 urllib.error 异常处理【1.3.1】 URLError如果打开一个不存在的页面,就会出现 URLError 错误,该错误有一个 reason 属性,用于返回错误的原因。简单举例:12345from urllib import request, error try: response = request.urlopen('https://www.itrhx.com/index/') except error.URLError as e: print(e.reason) 输出结果:1Not Found 【1.3.2】 HTTPErrorURLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。它有如下3个属性: code:返回 HTTP 状态码,比如 404 表示网页不存在,500 表示服务器内部错误等。 reason:同父类一样,用于返回错误的原因。 headers:返回请求头。 简单举例:12345from urllib import request, error try: response = request.urlopen('https://www.itrhx.com/index/') except error.HTTPError as e: print(e.code, e.reason, e.headers) 输出结果:123456789101112131415161718404 Not Found Content-Type: text/html; charset=utf-8Server: GitHub.comETag: \"5d57f030-7f2\"Access-Control-Allow-Origin: *X-Proxy-Cache: MISSX-GitHub-Request-Id: 4B46:2F5D:6DE0F1:755BB2:5D5964C5Content-Length: 2034Accept-Ranges: bytesDate: Sun, 18 Aug 2019 14:50:41 GMTVia: 1.1 varnishAge: 252Connection: closeX-Served-By: cache-tyo19951-TYOX-Cache: HITX-Cache-Hits: 1X-Timer: S1566139842.563134,VS0,VE0Vary: Accept-EncodingX-Fastly-Request-ID: e9eb0a507be66a866bfaa7c5cc2e1c53b1f7ccab 【1.3.3】 进阶用法因为 URLError 是 HTTPError 的父类,所以可以先选择捕获子类的错误,再去捕获父类的错误,前面的代码改进:12345678910from urllib import request, error ​try: response = request.urlopen('https://www.itrhx.com/index/') except error.HTTPError as e: print(e.reason, e.code, e.headers) except error.URLError as e: print(e.reason) else: print('Request Successfully') 【1.4】 urllib.parse 解析 URL【1.4.1】 urllib.parse.urlencode()将字典参数序列化为 GET 请求参数,示例:12345678from urllib.parse import urlencodedata = { 'ie': 'utf-8', 'wd': 'TRHX',}base_url = 'http://www.baidu.com?'url = base_url + urlencode(data)print(url) 输出结果:1http://www.baidu.com?ie=utf-8&wd=TRHX 【1.4.2】 urllib.parse.parse_qs()与 urlencode() 相反,将 GET 请求参数反序列化转回字典,示例:123from urllib.parse import parse_qsquery = 'name=TRHX&age=20'print(parse_qs(query)) 输出结果:1{'name': ['TRHX'], 'age': ['20']} 【1.4.3】 urllib.parse.parse_qsl()将参数转化为元组组成的列表,示例:123from urllib.parse import parse_qslquery = 'name=TRHX&age=20'print(parse_qsl(query)) 输出 结果:1[('name', 'TRHX'), ('age', '20')] 【1.4.4】 urllib.parse.urlparse()对 URL 进行分段,返回 6 个结果,示例:123from urllib.parse import urlparseresult = urlparse('http://www.baidu.com/index.html;user?id=5#comment')print(type(result), result) 输出结果:1<class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment') 返回结果为 ParseResult 类型的对象,含 scheme、netloc、path、params、query 和 fragment 6 个部分,依次代表协议、域名、路径、参数、查询条件、锚点 【1.4.5】 urllib.parse.urlunparse()与 urlparse() 相反,对 URL 进行组合,传入的参数是一个可迭代对象,长度必须是 6,否则会抛出参数数量不足或者过多的问题,示例:123from urllib.parse import urlunparse data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment'] print(urlunparse(data)) 输出结果:1http://www.baidu.com/index.html;user?a=6#comment 【1.4.6】 urllib.parse.urlsplit()与 urlparse() 方法相似,但是它不再单独解析 params 部分,只返回 5 个结果。params 会合并到 path 中,示例:123from urllib.parse import urlsplit result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment') print(result) 输出结果:1SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment') 【1.4.7】 urllib.parse.urlunsplit()与 urlunparse() 方法类似,对 URL 进行组合,传入的参数也是一个可迭代对象,长度必须为 5,示例:123from urllib.parse import urlunsplit data = ['http', 'www.baidu.com', 'index.html', 'a=6', 'comment'] print(urlunsplit(data)) 输出结果:1http://www.baidu.com/index.html?a=6#comment 【1.4.8】 urllib.parse.urljoin()对 URL 进行组合,提供两个 URL 作为两个参数,将会自动分析 URL 的 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果,示例:123456789from urllib.parse import urljoin print(urljoin('http://www.baidu.com', 'friends.html')) print(urljoin('http://www.baidu.com', 'https://www.itrhx.com/friends.html')) print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html')) print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html?id=2')) print(urljoin('http://www.baidu.com?wd=trhx', 'https://www.itrhx.com/index.html')) print(urljoin('http://www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com', '?category=2#comment')) print(urljoin('www.baidu.com#comment', '?category=2')) 输出结果:12345678http://www.baidu.com/friends.htmlhttps://www.itrhx.com/friends.htmlhttps://www.itrhx.com/friends.htmlhttps://www.itrhx.com/friends.html?id=2https://www.itrhx.com/index.htmlhttp://www.baidu.com?category=2#commentwww.baidu.com?category=2#commentwww.baidu.com?category=2 【1.4.9】 urllib.parse.quote()将内容转化为 URL 编码的格式。当 URL 中带有中文参数时,可以将中文字符转化为 URL 编码,示例:1234from urllib.parse import quotekeyword = '中国' url = 'https://www.baidu.com/s?wd=' + quote(keyword) print(url) 输出结果:1https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD 【1.4.10】 urllib.parse.unquote()与 quote() 方法相反,对 URL 进行解码,示例:123from urllib.parse import unquote url = 'https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD' print(unquote(url)) 输出结果:1https://www.baidu.com/s?wd=中国 【1.5】 urllib.robotparser 爬取权限判断【1.5.1】 Robots 协议简介 Robots 协议即爬虫协议,用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。它通常是一个叫作 robots.txt 的文本文件,一般放在网站的根目录下。 robots.txt 基本格式:123User-agent:Disallow:Allow: User-agent 为搜索爬虫的名称,设置为 * 则表示对任何爬虫皆有效; Disallow 指定了不允许抓取的目录,设置为 / 则代表不允许抓取所有页面; Allow 指定了允许抓取的目录,一般和 Disallow 一起使用,一般不会单独使用,用来排除某些限制。 一些常见的搜索爬虫名称及其对应的网站: 爬虫名称 网站名称 网站地址 BaiduSpider 百度 www.baidu.com Googlebot 谷歌 www.google.com 360Spider 360 www.so.com Sogouspider 搜狗 www.sogou.com YodaoBot 有道 www.youdao.com Bingbot 必应 www.bing.com Yahoo! Slurp 雅虎 www.yahoo.com ia_archiver Alexa www.alexa.cn Scooter altavista www.altavista.com 【1.5.2】 RobotFileParser 类常用方法RobotFileParser 类的声明:1urllib.robotparser.RobotFileParser(url='') 常用方法及其解释: set_url:用来设置 robots.txt 文件的链接。如果在创建 RobotFileParser对象时传入了链接,那么就不需要再用这种方法了。 read:读取 robots.txt 文件并进行分析。此方法执行一个读取和分析操作,若不调用此方法,接下来的判断都会为 False,这个方法不会返回任何内容,但是执行了读取操作。 parse:解析 robots.txt 文件,传入的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容。 can_fetch:该方法传入两个参数,第一个是 User-agent,第二个是要抓取的 URL。返回的内容是该搜索引擎是否可以抓取这个 URL,返回结果是 True 或 False。 mtime:返回的是上次抓取和分析 robots.txt 的时间,此方法可以定期检查来抓取最新的 robots.txt。 modified:将当前时间设置为上次抓取和分析 robots.txt 的时间。 以简书为例:123456from urllib.robotparser import RobotFileParserrp = RobotFileParser()rp.set_url('http://www.jianshu.com/robots.txt')rp.read()print(rp.can_fetch('*', 'https://www.jianshu.com/p/6d9527300b4c'))print(rp.can_fetch('*', \"http://www.jianshu.com/search?q=python&page=1&type=collections\")) 输出结果:12FalseFalse","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"爬虫学习","slug":"Python3-学习笔记/爬虫学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/爬虫学习/"}],"tags":[{"name":"爬虫","slug":"爬虫","permalink":"https://www.itrhx.com/tags/爬虫/"},{"name":"urllib","slug":"urllib","permalink":"https://www.itrhx.com/tags/urllib/"}]},{"title":"一个 JS 脚本实现网站预加载,提升页面加载速度","slug":"A24-instant.page","date":"2019-08-23T01:27:49.948Z","updated":"2019-09-09T13:44:52.383Z","comments":true,"path":"2019/08/23/A24-instant.page/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A24-instant.page/","excerpt":"instant.page 使用即时预加载技术,在用户点击之前预先加载页面。当用户的鼠标悬停在一个链接上超过 65 毫秒时,浏览器会对此页面进行预加载,当用户点击链接后,就从预加载的缓存中直接读取页面内容,从而达到缩短页面加载时间的目的。","text":"instant.page 使用即时预加载技术,在用户点击之前预先加载页面。当用户的鼠标悬停在一个链接上超过 65 毫秒时,浏览器会对此页面进行预加载,当用户点击链接后,就从预加载的缓存中直接读取页面内容,从而达到缩短页面加载时间的目的。 以我博客为例,使用了这项技术后,当鼠标在一个链接停留超过 65 毫秒时,Network 里可以看见相关文章已经预加载出来了,而停留时间过短就不会预加载(红色部分,状态为 canceled) 使用方法:将以下HTML代码放在</ body> 之前即可:1<script src=\"//instant.page/1.2.2\" type=\"module\" integrity=\"sha384-2xV8M5griQmzyiY3CDqh1dn4z3llDVqZDqzjzcY+jCBCk/a5fXJmuZ/40JJAPeoU\"></script> 但是此脚本是官方的,储存在国外服务器,对国内访问不太友好,可以将该JS脚本储存到自己的服务器上,点此获取该JS脚本,然后再根据以下格式在</ body> 之前引用:1<script src=\"`存放路径`/instantclick-1.2.2.js\" type=\"module\"></script> 也可以直接使用我的,使用 jsDeliver CDN 加速,速度还可以:1<script src=\"https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.2/js/instantclick-1.2.2.js\" type=\"module\"></script> 参考资料:《网站预加载 JS 脚本 instant.page》——by 左岸 ;instant.page官网","categories":[{"name":"WEB前端","slug":"WEB前端","permalink":"https://www.itrhx.com/categories/WEB前端/"}],"tags":[{"name":"instant.page","slug":"instant-page","permalink":"https://www.itrhx.com/tags/instant-page/"},{"name":"JS 预加载","slug":"JS-预加载","permalink":"https://www.itrhx.com/tags/JS-预加载/"}]},{"title":"网站ICP备案和公安备案流程","slug":"A23-beian","date":"2019-08-23T01:27:49.803Z","updated":"2020-03-14T06:07:46.135Z","comments":true,"path":"2019/08/23/A23-beian/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A23-beian/","excerpt":"","text":"网站备案分为ICP备案和公安备案 ICP备案:ICP备案的目的就是为了防止在网上从事非法的网站经营活动,打击不良互联网信息的传播,如果网站不备案的话,很有可能被查处以后关停。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》条例,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款或关闭。 公安备案:网站备案是根据国家法律法规需要网站的所有者向国家有关部门申请的备案,公安局备案是其中一种。公安局备案一般按照各地公安机关指定的地点和方式进行,操作流程会比ICP备案流程简单,主要是已登记为主。 以百度官网为例,其中京公安网备11000002000001就是公安备案,京ICP证030173号就是ICP备案 – ICP备案 一般在域名服务商那里都会有代备案系统,下面以阿里云为例,进入备案系统: 1、填写信息验证备案类型备案主办单位填写,个人就选个人,企业就选企业,按照实际信息填写: 2、产品验证对搭建备案网站的云服务器进行验证,如果你在阿里云购买了相关产品,就选择相应的产品类型和实例进行验证,也可以勾选已有备案服务号,填写服务号进行验证,备案服务号可以通过备案控制台进行申请,具体操作可以参考官方文档《申请备案服务号》,也有的小伙伴没有在任何地方购买过服务器等相关产品,比如单纯搭建一个 Github Pages + Hexo 轻量级的个人博客,这种博客没有后端,不需要服务器,但是要备案怎么办?这种情况也好解决,去某宝买一个服务号就行了。 3、填写网站信息填写网站信息以及办理备案的个人或者单位的真实信息,在填写网站名称的时候要特别注意!特别注意!特别注意!不满足要求的话是会被打回的!不能使用姓名、地名、成语、不能包含公司、组织等企业性质的词语……具体要求可以参考官方文档《填写主体信息和网站信息》。 4、上传资料根据要求,上传证件照片或证件彩色扫描件。身份证好说,拍好了上传就行了,注意《网站备案信息真实性核验单》需要你下载并打印在一张A4纸上,使用黑色签字笔填写,不能涂改,具体可参照所给的示例进行填写,填写完成后再拍照上传。企业网站类似,提交备案后会在一个工作日内进行初审。 5、人脸核验或幕布拍照核验根据不同地域管局要求及核验平台的支持情况,使用人脸识别进行核验,或者申请专用幕布进行幕布拍照核验 地区 核验要求 上海、福建地区用户 需使用阿里云APP进行人脸核验。如果使用PC端发起的备案申请,请根据界面提示下载阿里云APP进行人脸核验。 广东、辽宁、安徽、重庆地区用户 首次备案、新增网站:支持使用阿里云APP进行人脸核验或通过阿里云备案平台(PC端)进行幕布拍照核验。其他备案类型:需通过阿里云备案平台(PC端)进行幕布拍照核验。 其他地区用户 通过阿里云备案平台(PC端)进行幕布拍照核验。 以幕布拍照核验为例,如果你没有阿里云的幕布,就需要申请幕布(免费的),邮寄很快,大约两三天就到了,等收到幕布后,按照要求进行拍照,一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!不合格依旧会被打回!拍照完成后上传即可。 6、提交管局、短信核验当照片审核通过后,就会提交到管局,工信部要求部分省市成为手机号码短信核验试点省市,相应省市的用户在阿里云备案平台提交备案申请且初审完成后,会收到工信部发送的核验短信,短信包含验证码和验证地址,需要在收到短信的24小时内完成短信核验,备案申请才能进入管局审核。需短信核验省份: 2017年12月18日起:天津、甘肃、西藏、宁夏、海南、新疆、青海被列为试点省份。 2018年9月10日起:浙江、四川、福建、陕西、重庆、广西、云南被列为试点省份。 2018年9月24日起:山东、河南、安徽、湖南、山西、黑龙江、内蒙古、湖北被列为试点省份。 7、ICP备案完成整个备案过程中会有阿里云的客服打电话给你,进行信息确认,备案申请信息成功提交管局系统后,管局审核一般为 3 - 20 个工作日(亲测很快,不到一个周就通过了),审核通过后会收到阿里云的邮件通知。 – 公安备案 公安备案个人觉得比ICP备案还要麻烦,自己在公安备案的时候,最开始申请了一个月也没给我处理(大概是地方原因,所在的市比较小,估计都没几个人办过网站,网警也不太负责),与ICP备案最大的不同,如果你是交互式网站的话,公安备案是需要你去公安机关当面审核的,这也是比较麻烦的一点。 1、用户注册、登录登录全国互联网安全管理服务平台,选择联网备案登录,注册账号并登录 2、新办网站备案申请点击新办网站申请,按实填写网站开办主体,上传身份证正反照和手持身份证件照。 3、填写网站基本信息按实填写网站基本信息,需要注意的地方: IP:IP地址为阿里云/腾讯云的公网IP地址,请不要填写内网IP。 域名证书:以阿里云为例,进入【域名控制台】,点击域名后面的【管理】,选择【域名证书下载】即可,其它服务商类似。 网络接入/域名注册服务商:若办理公安备案的域名是通过阿里云完成的工信部备案,则按照以下填写:网络接入服务商: 接入商所属地区管辖:境内 接入商所属区域 :浙江省 杭州市 滨江区 名称:阿里云计算有限公司 网站接入方式:租赁虚拟空间 域名注册服务商: 域名商所属地区管辖:境内 域名服务商所属区域:浙江省 杭州市 余杭区 名称:阿里云计算有限公司(原万网) 也可以通过点击后面的查询网络接入\\域名注册服务商直接选择相应服务商,其他服务商类似 服务类型:交互式服务指:为互联网用户提供信息发布、交流互动等服务,包括但不限于论坛、博客、微博、网络购物、网上支付等服务类型,此项选择是否提供互联网交互服务将会直接影响到后面是否需要去公安局当面核验,若选择是,当地网警会打电话叫你去公安局当面核验,还需要填写《交互式服务安全检查表》等各种文件,总之是比较麻烦的,个人小网站,博客什么的建议选择否,选择www服务,这样的话不用去当面核验,审核下来也比较快,企业单位用户建议选择交互式。 其他信息如实填写即可! 4、填写网站负责人信息填写网站安全负责人和网站应急联络人相关信息,网站应急联络人直接勾选同主体负责人后会自动填入。 5、同意责任书并提交审核《互联网信息服务单位网络安全责任告知书》有30秒的强制阅读时间,建议认真阅读一下告知书的内容。然后勾选我已阅读,点击提交即可。随后可以看到审核状态,不同地区政策有所不同,会有当地的网警联系网站负责人的,审核通过后记得在网站首页底部张贴公安机关核发的备案图标!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"},{"name":"WEB前端","slug":"WEB前端","permalink":"https://www.itrhx.com/categories/WEB前端/"}],"tags":[{"name":"ICP备案","slug":"ICP备案","permalink":"https://www.itrhx.com/tags/ICP备案/"},{"name":"公安备案","slug":"公安备案","permalink":"https://www.itrhx.com/tags/公安备案/"}]},{"title":"恶意刷留言者——你是什么垃圾?","slug":"A25-SB","date":"2019-08-23T01:27:46.962Z","updated":"2020-03-14T06:11:57.038Z","comments":true,"path":"2019/08/23/A25-SB/","link":"","permalink":"https://www.itrhx.com/2019/08/23/A25-SB/","excerpt":"","text":"有一种动物,自认为自己技术了得,实则和CXK差不多,以攻击他人为乐,这种动物称为程序员中的垃圾,哦!不,这种动物称不上程序员! 这个周连续被人刷垃圾评论,具体开始时间不记得了,不想多说什么,太多的文字用在垃圾身上简直是玷污中华上下五千年的文化,只问一句,你是什么垃圾? 随便提一句,这家伙连我的情侣博客一起刷的,真是让人大跌眼镜啊,估计自己没女朋友吧,见不得别人好,悲催啊,不知道又是哪个学校,哪个公司,哪个家庭摊上了这种垃圾。 请问你是什么垃圾?垃圾分类,从我做起!","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"垃圾","slug":"垃圾","permalink":"https://www.itrhx.com/tags/垃圾/"}]},{"title":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS","slug":"A28-hexo-add-https","date":"2019-08-11T14:16:09.175Z","updated":"2019-12-31T15:24:54.792Z","comments":true,"path":"2019/08/11/A28-hexo-add-https/","link":"","permalink":"https://www.itrhx.com/2019/08/11/A28-hexo-add-https/","excerpt":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS","text":"利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。 HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。 目前大多数基于 GitHub Pages 的 Hexo 博客都是利用 CloudFlare 的 CDN 中转来启用 HTTPS 的,实现方法可以参考我的文章:《利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持》,这样的做法确实可以起到开启HTTPS的目的,但是这样做也有弊端,你会发现 CDN 中转,国外访问的话,可以起到加速的作用,但是国内访问反而速度降低了,还不如直接连接GitHub呢 其实 GitHub 官方是支持自定义域名开启 HTTPS 的,之前我和大多数人一样,以为只有 GitHub Pages 自带的域名(xxx.github.io)才能开启 HTTPS,直到有一天我发现了官方在2018年5月1日发表的博客:《Custom domains on GitHub Pages gain support for HTTPS》,大概讲的意思就是从8月份开始, GitHub Pages 上的自定义域名也能开启 HTTPS 了,下面就具体介绍一下如何实现 如果你以前域名的记录类型是 CNAME 方式,那么就不需要做任何更改如果你以前域名的记录类型是 A 方式,那么就需要把记录值指向以下IP地址: 185.199.108.153 185.199.109.153 185.199.110.153 185.199.111.153 修改好记录值后,我们需要再次来到你博客的 GitHub 仓库,在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】,注意,如果此时你不能勾选,请删除【Custom domain】里面你的域名并点击【Save】保存,刷新网页后就可以勾选了,然后在把域名填进去并保存即可,短时间可能会出现不安全的提示,这是因为加密证书大概一个小时左右才会生效,等一会儿就好了 最后贴一个我的域名解析,可作为参考:","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"HTTPS","slug":"HTTPS","permalink":"https://www.itrhx.com/tags/HTTPS/"}]},{"title":"Github+jsDelivr+PicGo 打造稳定快速、高效免费图床","slug":"A27-image-hosting","date":"2019-07-31T16:02:08.497Z","updated":"2020-03-14T06:17:16.724Z","comments":true,"path":"2019/08/01/A27-image-hosting/","link":"","permalink":"https://www.itrhx.com/2019/08/01/A27-image-hosting/","excerpt":"","text":"– 前言图床是个啥东西就不用过多介绍了,先来对比一下各路图床: 微博图床:以前用的人比较多,从2019年4月开始开启了防盗链,凉凉 SM.MS:运营四年多了,也变得越来越慢了,到了晚上直接打不开图片,速度堪忧 其他小众图床:随时有挂掉的风险 Imgur等国外图床:国内访问速度太慢,随时有被墙的风险 大厂储存服务:例如七牛云、又拍云、腾讯云COS、阿里云OSS等,容量限制,操作繁琐,又是实名认证又是域名备案的,麻烦,而且还要花钱(有钱又不怕麻烦的当我没说) 因此,GitHub 图床是个不错的选择,利用 jsDelivr CDN 加速访问(jsDelivr 是一个免费开源的 CDN 解决方案),PicGo 工具一键上传,操作简单高效,GitHub 和 jsDelivr 都是大厂,不用担心跑路问题,不用担心速度和容量问题,而且完全免费,可以说是目前免费图床的最佳解决方案! – 新建GitHub仓库登录/注册GitHub,新建一个仓库,填写好仓库名,仓库描述,根据需求选择是否为仓库初始化一个README.md描述文件 – 生成一个Token在主页依次选择【Settings】-【Developer settings】-【Personal access tokens】-【Generate new token】,填写好描述,勾选【repo】,然后点击【Generate token】生成一个Token,注意这个Token只会显示一次,自己先保存下来,或者等后面配置好PicGo后再关闭此网页 – 配置PicGo前往下载PicGo,安装好后开始配置图床 设定仓库名:按照【用户名/图床仓库名】的格式填写 设定分支名:【master】 设定Token:粘贴之前生成的【Token】 指定存储路径:填写想要储存的路径,如【ITRHX-PIC/】,这样就会在仓库下创建一个名为 ITRHX-PIC 的文件夹,图片将会储存在此文件夹中 设定自定义域名:它的作用是,在图片上传后,PicGo 会按照【自定义域名+储存路径+上传的图片名】的方式生成访问链接,并放到粘贴板上,因为我们要使用 jsDelivr 加速访问,所以可以设置为【https://cdn.jsdelivr.net/gh/用户名/图床仓库名 】,上传完毕后,我们就可以通过【https://cdn.jsdelivr.net/gh/用户名/图床仓库名/图片路径 】加速访问我们的图片了,比如上图的图片链接为:https://cdn.jsdelivr.net/gh/TRHX/ImageHosting/ITRHX-PIC/A27/08.png 关于 jsDelivr 具体是如何引用资源的可以参考我的另一篇博客:《免费CDN:jsDelivr+Github》 – 进行高效创作配置好PicGo后,我们就可以进行高效创作了,将图片拖拽到上传区,将会自动上传并复制访问链接,将链接粘贴到博文中就行了,访问速度杠杠的,此外PicGo还有相册功能,可以对已上传的图片进行删除,修改链接等快捷操作,PicGo还可以生成不同格式的链接、支持批量上传、快捷键上传、自定义链接格式、上传前重命名等,更多功能自己去探索吧!","categories":[{"name":"图床","slug":"图床","permalink":"https://www.itrhx.com/categories/图床/"}],"tags":[{"name":"jsDelivr","slug":"jsDelivr","permalink":"https://www.itrhx.com/tags/jsDelivr/"},{"name":"图床","slug":"图床","permalink":"https://www.itrhx.com/tags/图床/"},{"name":"PicGo","slug":"PicGo","permalink":"https://www.itrhx.com/tags/PicGo/"}]},{"title":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持","slug":"A26-hexo-add-https","date":"2019-07-31T03:48:43.215Z","updated":"2020-03-14T06:13:57.930Z","comments":true,"path":"2019/07/31/A26-hexo-add-https/","link":"","permalink":"https://www.itrhx.com/2019/07/31/A26-hexo-add-https/","excerpt":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持","text":"利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持 HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。 HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。 – 前言GitHub Pages 自带的域名(xxx.github.io)支持开启 https 服务,可以在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】即可,但是如果你设置了自定义域名的话,就比较复杂了,因为 hexo 博客是托管在 GitHub 上的,没有自己的服务器,因此也不支持上传 SSL 证书,从2018年5月1日起,GitHub官方也支持自定义域名开启https了,实现方法可参考我的文章:《利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS》,另外一种方法就是利用 Cloudflare 的 CDN 中转来启用 HTTPS,这种方法的弊端就是国内访问速度可能会变慢,本文主要讲述这种方法 Cloudflare 是一家美国的跨国科技企业,以向客户提供网站安全管理、性能优化及相关的技术支持为主要业务,它提供了免费的 https 服务,注意不是应用SSL证书,实现原理:用户到CDN服务器的连接为 https 方式,而CDN服务器到 GithubPages 服务器的连接为 http 方式,在CDN服务器那里加上反向代理 – 注册 Cloudflare到 Cloudflare官网 注册账号 – 添加站点添加你的站点,一直下一步即可 如果你已经在域名服务商那里解析过域名的话,之后就会出现你域名的解析列表,如果还没有解析过,可以参考《为hexo博客配置个性域名》 –修改DNS点击下一步 Cloudflare 会提供给你两个 DNS 地址 到域名服务商那里修改DNS,以阿里云为例,依次选择【控制台】-【域名】,选择你的域名,点击【管理】-【修改DNS】,将上面 Cloudflare 提供的两个 DNS 地址填进去,会过几分钟才生效 –开启 HTTPS在 Cloudflare 管理页面,点击【Crypto】选项,选择 SSL 的模式为【full】,注意:在CloudFlare 上激活站点后,可能需要24小时才能颁发新证书,耐心等待即可 关于三种模式 Flexible、Full、Full (Strict) 的区别: Flexible:访客与 Cloudflare 之间是加密的,Cloudflare 到站点服务器是不加密的 Full:访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它不会验证你服务器上的证书是否合法,因此你可以在你服务器上安装任何证书,包括自签名证书 Full (strict):访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它会验证你服务器上的证书是否合法,你必须在你的服务器上安装有可信赖的CA证书,并且这个证书必须是未过期,包含有域名等信息的 至此,我们的域名就支持 https 访问了,但是当用户输入 http://xxxxxx 访问时,浏览器依旧会以 http 协议来访问,并不会跳转到 https,这时候就需要利用重定向来解决了 –重定向强制 HTTPSCloudflare 提供了一个名叫 Page Rules 的页面规则的功能,我们可以利用此功能对 URL 做一些处理,当用户访问是 HTTP 的时候重定向到 HTTPS,点击【Page Rules】选项,点击【Create Page Rules】,新建如下规则并保存即可 现在我们的 Hexo 博客就实现了全站 HTTPS!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"HTTPS","slug":"HTTPS","permalink":"https://www.itrhx.com/tags/HTTPS/"}]},{"title":"Eclipse 通过 JDBC 连接 SQL Server","slug":"A22-eclipse-connects-to-sql","date":"2019-05-13T18:21:23.785Z","updated":"2020-03-14T05:59:46.063Z","comments":true,"path":"2019/05/14/A22-eclipse-connects-to-sql/","link":"","permalink":"https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/","excerpt":"","text":"本文用到的软件版本以及相关环境: Eclipse Photon Release (4.8.0)JDK-10.0.2SQL Server 2012 1.配置 SQL Server 2012打开 SQL Server Management Studio,使用 SQL Server 身份验证 登录: 如果在安装 SQL Server 2012 时选用了Windows身份验证登录方式,则需要重新设置,设置方法参考:《SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】》 登录成功后,打开 SQL Server 配置管理器: 在左边找到 SQL Server 网络配置,点击【你的数据库名】的协议,将右边栏的 Shared Memory、Named Pipes、TCP/IP 全部右键选择启用: 双击 TCP/IP(或者右键选择属性),选择【IP地址】,将【IP1】和【IP10】的【IP地址】设为 127.0.0.1 将所有【IPx】(IP1、IP10、IP11、IP12等)的【已启用】设为是 下拉到窗口底部,将 【IPAll】 中的【TCP端口】设成 1433,其余不变 2.开启 Telnet 服务打开【控制面板】,选择【程序】,点击【启用或关闭 Windows 功能】,找到【Telnet Client】勾选并保存,Windows 7 或者以下的版本则勾选【Telnet 服务器】和【Telnet 客户端】 3.测试1433端口是否打开 运行cmd,输入 telnet 127.0.0.1 1433,若提示连接失败,则说明1433端口没有打开,需要重新进行以上配置,若连接成功,则显示如下: 4.下载JDBC 点击此处下载各个版本JDBC,不同版本的JDBC驱动程序适用的JAR不同,与不同版本的SQL兼容性也不同,具体参考《Microsoft SQL Server JDBC 驱动程序支持矩阵》,比如使用 SQL Server 2012 我们可以下载6.0的版本,下载sqljdbc_6.0.8112.200_chs.tar.gz文件,解压后可以找到sqljdbc41.jar与sqljdbc42.jar文件,使用时要注意自己JDK是哪个版本的,1.80以上的则对应 sqljdbc42.jar 类库 5.Eclipse 连接 SQL Server将 sqljdbc41.jar 或者 sqljdbc42.jar 放到一个文件夹下,打开 Eclipse,在需要连接数据库的项目里,右键【src】,选择【Build Path】、【Configure Build Path…】,在弹出的窗口选择【Libraries】,选择【Modulepath】,单击【Add External JARs…】,找到下载的 sqljdbc41.jar 或者 sqljdbc42.jar 文件并打开,然后【Apply and Close】保存 6.测试连接打开 SQL Server 2012,在其中新建数据库 test Eclipse中,在项目下新建一个package,再新建一个class,用于测试数据库的连接:12345678910111213141516171819202122package test;import java.sql.*;public class Main { public static void main(String [] args) { String driverName=\"com.microsoft.sqlserver.jdbc.SQLServerDriver\"; String dbURL=\"jdbc:sqlserver://localhost:1433;DatabaseName=test\"; //要连接的数据库名 String userName=\"sa\"; //数据库用户名 String userPwd=\"000000\"; //数据库密码 try { Class.forName(driverName); Connection dbConn=DriverManager.getConnection(dbURL,userName,userPwd); System.out.println(\"连接数据库成功\"); } catch(Exception e) { e.printStackTrace(); System.out.print(\"连接失败\"); } } } 如果以上所有操作正确,就能成功连接数据库了:","categories":[{"name":"Java","slug":"Java","permalink":"https://www.itrhx.com/categories/Java/"}],"tags":[{"name":"JDBC","slug":"JDBC","permalink":"https://www.itrhx.com/tags/JDBC/"},{"name":"SQL Server 2012","slug":"SQL-Server-2012","permalink":"https://www.itrhx.com/tags/SQL-Server-2012/"},{"name":"Elicpse","slug":"Elicpse","permalink":"https://www.itrhx.com/tags/Elicpse/"}]},{"title":"Python PEP8 代码规范常见问题及解决方法","slug":"A21-PEP8","date":"2019-04-14T17:09:58.738Z","updated":"2019-09-24T12:47:14.895Z","comments":true,"path":"2019/04/15/A21-PEP8/","link":"","permalink":"https://www.itrhx.com/2019/04/15/A21-PEP8/","excerpt":"之前一直用 Python IDLE 写代码,最近换成 PyCharm 写代码总是会出现波浪号,这才了解到 Python 的 PEP8 代码规范,所以将常见的 PEP8 代码规范问题和解决方法记录一下,学习一下,遇到了再持续更新,养成良好的习惯,编写规范的代码!","text":"之前一直用 Python IDLE 写代码,最近换成 PyCharm 写代码总是会出现波浪号,这才了解到 Python 的 PEP8 代码规范,所以将常见的 PEP8 代码规范问题和解决方法记录一下,学习一下,遇到了再持续更新,养成良好的习惯,编写规范的代码! PEP 8: no newline at end of file解决方法:代码末尾需要另起一行,光标移到最后回车即可 PEP 8: indentation is not a multiple of four解决方法:缩进不是4的倍数,检查缩进 PEP 8: over-indented解决方法:过度缩进,检查缩进 PEP 8: missing whitespace after’,’解决方法:逗号后面少了空格,添加空格即可,类似还有分号或者冒号后面少了空格 PEP 8: multiple imports on one line解决方法:不要在一句 import 中引用多个库,举例:import socket, urllib.error最好写成:import socket import urllib.error PEP 8: blank line at end of line解决方法:代码末尾行多了空格,删除空格即可 PEP 8: at least two spaces before inline comment解决方法:代码与注释之间至少要有两个空格 PEP 8: block comment should start with ‘#’解决方法:注释要以#加一个空格开始 PEP 8: inline comment should start with ‘#’解决方法:注释要以#加一个空格开始 PEP 8: module level import not at top of file解决方法:import不在文件的最上面,可能之前还有其它代码 PEP 8: expected 2 blank lines,found 0解决方法:需要两条空白行,添加两个空白行即可 PEP 8: function name should be lowercase解决方法:函数名改成小写即可 PEP 8: missing whitespace around operator解决方法:操作符(’=’、’>’、’<’等)前后缺少空格,加上即可 PEP 8: unexpected spaces around keyword / parameter equals解决方法:关键字/参数等号周围出现意外空格,去掉空格即可 PEP 8: multiple statements on one line (colon)解决方法:多行语句写到一行了,比如:if x == 2: print('OK')要分成两行写 PEP 8: line too long (82 > 79 characters)解决方法:超过了每行的最大长度限制79 如果想要选择性忽略PEP8代码风格的警告信息可以使用以下方法:(养成良好的习惯,编写规范的代码!不推荐忽略!) ①将鼠标移到出现警告信息的地方,按 alt+Enter,选择忽略(Ignore)这个错误即可:②依次选择 File - Settings - Editor - Inspections,在 Python下找到 PEP8 coding style violation 选项,在右下角的 Ignore errors 里点击加号可以添加需要忽略的警告信息ID(ID信息见后面附录),例如想要忽略indentation contains mixed spaces and tabs这个警告,只需要添加其ID:E101 即可附录:全部警告信息以及对应的ID,官方地址:https://pep8.readthedocs.io/en/latest/intro.html#error-codes code sample message E1 Indentation E101 indentation contains mixed spaces and tabs E111 indentation is not a multiple of four E112 expected an indented block E113 unexpected indentation E114 indentation is not a multiple of four (comment) E115 expected an indented block (comment) E116 unexpected indentation (comment) E117 over-indented E121 (*^) continuation line under-indented for hanging indent E122 (^) continuation line missing indentation or outdented E123 (*) closing bracket does not match indentation of opening bracket’s line E124 (^) closing bracket does not match visual indentation E125 (^) continuation line with same indent as next logical line E126 (*^) continuation line over-indented for hanging indent E127 (^) continuation line over-indented for visual indent E128 (^) continuation line under-indented for visual indent E129 (^) visually indented line with same indent as next logical line E131 (^) continuation line unaligned for hanging indent E133 (*) closing bracket is missing indentation E2 Whitespace E201 whitespace after ‘(‘ E202 whitespace before ‘)’ E203 whitespace before ‘:’ E211 whitespace before ‘(‘ E221 multiple spaces before operator E222 multiple spaces after operator E223 tab before operator E224 tab after operator E225 missing whitespace around operator E226 (*) missing whitespace around arithmetic operator E227 missing whitespace around bitwise or shift operator E228 missing whitespace around modulo operator E231 missing whitespace after ‘,’, ‘;’, or ‘:’ E241 (*) multiple spaces after ‘,’ E242 (*) tab after ‘,’ E251 unexpected spaces around keyword / parameter equals E261 at least two spaces before inline comment E262 inline comment should start with ‘# ‘ E265 block comment should start with ‘# ‘ E266 too many leading ‘#’ for block comment E271 multiple spaces after keyword E272 multiple spaces before keyword E273 tab after keyword E274 tab before keyword E275 missing whitespace after keyword E3 Blank line E301 expected 1 blank line, found 0 E302 expected 2 blank lines, found 0 E303 too many blank lines (3) E304 blank lines found after function decorator E305 expected 2 blank lines after end of function or class E306 expected 1 blank line before a nested definition E4 Import E401 multiple imports on one line E402 module level import not at top of file E5 Line length E501 (^) line too long (82 > 79 characters) E502 the backslash is redundant between brackets E7 Statement E701 multiple statements on one line (colon) E702 multiple statements on one line (semicolon) E703 statement ends with a semicolon E704 (*) multiple statements on one line (def) E711 (^) comparison to None should be ‘if cond is None:’ E712 (^) comparison to True should be ‘if cond is True:’ or ‘if cond:’ E713 test for membership should be ‘not in’ E714 test for object identity should be ‘is not’ E721 (^) do not compare types, use ‘isinstance()’ E722 do not use bare except, specify exception instead E731 do not assign a lambda expression, use a def E741 do not use variables named ‘l’, ‘O’, or ‘I’ E742 do not define classes named ‘l’, ‘O’, or ‘I’ E743 do not define functions named ‘l’, ‘O’, or ‘I’ E9 Runtime E901 SyntaxError or IndentationError E902 IOError W1 Indentation warning W191 indentation contains tabs W2 Whitespace warning W291 trailing whitespace W292 no newline at end of file W293 blank line contains whitespace W3 Blank line warning W391 blank line at end of file W5 Line break warning W503 (*) line break before binary operator W504 (*) line break after binary operator W505 (*^) doc line too long (82 > 79 characters) W6 Deprecation warning W601 .has_key() is deprecated, use ‘in’ W602 deprecated form of raising exception W603 ‘<>’ is deprecated, use ‘!=’ W604 backticks are deprecated, use ‘repr()’ W605 invalid escape sequence ‘x’ W606 ‘async’ and ‘await’ are reserved keywords starting with Python 3.7","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"Python","slug":"Python","permalink":"https://www.itrhx.com/tags/Python/"},{"name":"PEP8","slug":"PEP8","permalink":"https://www.itrhx.com/tags/PEP8/"}]},{"title":"VMware Pro 15 安装 Deepin15.9 国产操作系统","slug":"A20-install-deepin15.9","date":"2019-04-14T12:53:34.310Z","updated":"2020-03-14T05:53:55.551Z","comments":true,"path":"2019/04/14/A20-install-deepin15.9/","link":"","permalink":"https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/","excerpt":"","text":"Deepin是由武汉深之度科技有限公司开发的Linux发行版,个人认为其界面设计非常美观,而且作为国产操作系统,值得我们去体验和支持! 1.下载安装 VMware Workstation Pro 15 进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装 2.下载 Deepin15.9 系统 进入 deepin 官网,下载最新版 deepin 系统镜像 3.在 VMware 中创建虚拟机打开安装好的 VMware Workstation Pro 15,选择创建新的虚拟机在新建虚拟机向导中选择自定义(高级):默认直接下一步,直到出现下图,再选择稍后安装操作系统:选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本:更改虚拟机名称及存放位置:接下来为虚拟机指定处理器数量、分配内存(太大了可能会导致卡顿,太小了也不好,推荐内存大小即可)一直选择默认即可,选择磁盘时,选择创建新虚拟磁盘:选择将虚拟磁盘储存为单个文件:默认下一步:点击完成:此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Deepin,单击 Deepin,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Deepin 15.9 镜像文件,点击“确定” 4.在虚拟机上安装 Deepin 系统单击 Deepin,选择“开启此虚拟机”接下来就是选择语言、创建用户、选择时区、指定磁盘等过程:安装完成后:可以看见界面还是相当美观的,系统也自带了深度的一些软件,比如深度录屏,深度录音,深度影院,深度计算器等等的一些小工具,作为国产操作系统,个人觉得已经非常优秀了,值得去体验!","categories":[{"name":"Linux","slug":"Linux","permalink":"https://www.itrhx.com/categories/Linux/"}],"tags":[{"name":"VMware","slug":"VMware","permalink":"https://www.itrhx.com/tags/VMware/"},{"name":"Deepin","slug":"Deepin","permalink":"https://www.itrhx.com/tags/Deepin/"}]},{"title":"Windows 系统中 Pygame 的安装","slug":"A19-install-pygame","date":"2019-03-10T14:34:09.591Z","updated":"2019-09-24T12:47:07.382Z","comments":true,"path":"2019/03/10/A19-install-pygame/","link":"","permalink":"https://www.itrhx.com/2019/03/10/A19-install-pygame/","excerpt":"","text":"Pygame是跨平台Python模块,专为电子游戏设计,可用于管理图形、动画乃至声音,建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚,通过使用Pygame来处理在屏幕上绘制图像等任务,你不用考虑众多繁琐而艰难的编码工作,而是将重点放在程序的高级逻辑上。 你可以从以下三个地址查找与你运行的Python版本相匹配的Windows安装程序: https://bitbucket.org/pygame/pygame/downloads/ (Pygame项目托管在代码分享网站Bitbucket中) http://www.pygame.org/download.shtml (Pygame官网) https://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame (如果以上两个地址找不到合适的安装程序,推荐去这个) 如果下载的是.exe文件,直接运行它,如果下载的是.whl文件,就需要打开命令窗口,切换到该文件所在的目录,使用pip来运行它: 首先检查电脑是否安装了pip,打开终端窗口,执行如下命令:1>python -m pip --version 如果输出版本信息则已安装:1>pip 18.1 from E:\\Python\\lib\\site-packages\\pip (python 3.6) 否则请安装pip,访问 https://bootstrap.pypa.io/get-pip.py ,如果出现对话框请直接保存文件,如果出现的是get-pip.py的源代码,则需要新建一个get-pip.py文件,将该代码复制粘贴到其中,使用下面的命令运行get-pip.py:1>python get-pip.py 安装完成后可再次使用python -m pip --version命令检查是否成功安装了pip,成功安装pip后,使用以下命令来安装Pygame:(注意要先cd到你下载的文件的目录)1>python -m pip install --user 下载的.whl文件名 出现以下信息则表示安装成功:1>Successfully installed 你安装的Pygame版本 比如我的Python版本是3.6.5,64位的,则需要下载pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl,该文件保存到了桌面,使用下面的命令安装Pygame:12345678C:\\Users\\Lenovo>cd desktopC:\\Users\\Lenovo\\Desktop>python -m pip install --user pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whlProcessing c:\\users\\lenovo\\desktop\\pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whlInstalling collected packages: pygameSuccessfully installed pygame‑1.9.4C:\\Users\\Lenovo>Desktop> 检查是否成功安装Pygame:在Python的IDLE里输入import pygame,如果不报错,则安装成功,再输入pygame.ver就能看到版本号: ) 可能出现的问题:报错:xxxxxxxxxxxxxxxxxxxxxx.whl is not a supported wheel on this platform.原因:Python版本与Pygame版本不对应解决方法:Pygame文件名中的cp**表示Python对应的版本,另外并不是你电脑64位则下载64位,要看你安装的Python是否为64位,注意下载对应的版本! 报错:You are using pip version x.x.x, however version x.x.x is available.You should consider upgrading via the 'python -m pip install --upgrade pip' command.原因:版本需要更新解决方法:输入python -m pip install --upgrade pip命令进行更新即可 Pygame安装完成后我们就可以使用Python来开发游戏啦!","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"学习经验","slug":"Python3-学习笔记/学习经验","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/学习经验/"}],"tags":[{"name":"Pygame","slug":"Pygame","permalink":"https://www.itrhx.com/tags/Pygame/"},{"name":"Python","slug":"Python","permalink":"https://www.itrhx.com/tags/Python/"}]},{"title":"免费CDN:jsDelivr + Github","slug":"A18-free-cdn","date":"2019-02-10T14:30:17.903Z","updated":"2020-03-14T05:51:31.881Z","comments":true,"path":"2019/02/10/A18-free-cdn/","link":"","permalink":"https://www.itrhx.com/2019/02/10/A18-free-cdn/","excerpt":"","text":"本文有参考《jsDelivr+github使用教程,免费好用的cdn》—— By hojun CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。——百度百科 放在Github的资源在国内加载速度比较慢,因此需要使用CDN加速来优化网站打开速度,jsDelivr + Github便是免费且好用的CDN,非常适合博客网站使用。 1、新建Github仓库 2、克隆Github仓库到本地 点击 Clone or download,一键复制仓库地址 在本地目录右键 Git Bash Here,执行以下命令: 1git clone 一键复制的仓库地址 3、上传资源 复制需要上传的资源到本地git仓库(注:jsDelivr不支持加载超过20M的资源),在本地git仓库目录下右键 Git Bash Here,执行以下命令:1234git status //查看状态git add . //添加所有文件到暂存区git commit -m '第一次提交' //把文件提交到仓库git push //推送至远程仓库 4、发布仓库 点击release发布 自定义发布版本号 5、通过jsDelivr引用资源 使用方法:https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径例如:https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@1.0/images/trhx.png    https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.1/css/style.css    https://cdn.jsdelivr.net/gh/moezx/cdn@3.1.3//The%20Pet%20Girl%20of%20Sakurasou.mp4 注意:版本号不是必需的,是为了区分新旧资源,如果不使用版本号,将会直接引用最新资源,除此之外还可以使用某个范围内的版本,查看所有资源等,具体使用方法如下: // 加载任何Github发布、提交或分支https://cdn.jsdelivr.net/gh/user/repo@version/file // 加载 jQuery v3.2.1https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/dist/jquery.min.js // 使用版本范围而不是特定版本https://cdn.jsdelivr.net/gh/jquery/jquery@3.2/dist/jquery.min.jshttps://cdn.jsdelivr.net/gh/jquery/jquery@3/dist/jquery.min.js // 完全省略该版本以获取最新版本https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js // 将“.min”添加到任何JS/CSS文件中以获取缩小版本,如果不存在,将为会自动生成https://cdn.jsdelivr.net/gh/jquery/jquery@3.2.1/src/core.min.js // 在末尾添加 / 以获取资源目录列表https://cdn.jsdelivr.net/gh/jquery/jquery/","categories":[{"name":"CDN","slug":"CDN","permalink":"https://www.itrhx.com/categories/CDN/"}],"tags":[{"name":"jsDelivr","slug":"jsDelivr","permalink":"https://www.itrhx.com/tags/jsDelivr/"},{"name":"CDN","slug":"CDN","permalink":"https://www.itrhx.com/tags/CDN/"}]},{"title":"新年快乐!","slug":"A17-happy-new-year","date":"2019-02-04T19:09:51.832Z","updated":"2019-09-09T14:11:08.281Z","comments":true,"path":"2019/02/05/A17-happy-new-year/","link":"","permalink":"https://www.itrhx.com/2019/02/05/A17-happy-new-year/","excerpt":"","text":"C printf("2019,祝大家"); C++ cout<<"一帆风顺"; C# System.Console.WriteLine("二龙腾飞") VB Msg("三羊开泰") VC MessageBox("四季平安"); Java System.out.println("五福临门"); JavaScript alert("六六大顺") PHP echo "七星高照"; Python print("八方来财") Html <br/>九运当头<br/> Objectivec NSLog(@"十全十美"); QBasic Print "阖家幸福" Asp Response.Write "心想事成" Ruby puts "财源广进" VBScript MsgBox "幸福安康" XML <TextView android:text="大展宏图" /> LUA print("学业有成") Delphi ShowMessage('万事如意'); shell echo 步步高升 perl print '鸿案齐眉' LISP (format t "身体健康~%") powerBuilder messagebox("龙马精神") COBOL DISPLAY '笑口常开' aswing JOptionPane.showMessageDialog("happy","好运连连") Android Toast.makeText(getApplicationContext(),"年年有余",Toast.LENGTH_SHORT).show() flex Alert.show("大吉大利"); Foxpro ?[家庭幸福!] iapp tw("瑞气盈门") DOS批处理 echo 鹏程万里 易语言 调试输出(“万事亨通”) Clojure (println "年年有今昔") verilog/systemverilog/e $display("岁岁有今朝") as trace("祝大家新年快乐!");","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/tags/BLOG/"}]},{"title":"一台电脑使用两个/多个GitHub账号部署两个/多个Hexo博客","slug":"A16-deploy-two-or-more-hexo-blogs","date":"2019-01-18T13:23:24.345Z","updated":"2020-02-07T07:30:20.946Z","comments":true,"path":"2019/01/18/A16-deploy-two-or-more-hexo-blogs/","link":"","permalink":"https://www.itrhx.com/2019/01/18/A16-deploy-two-or-more-hexo-blogs/","excerpt":"由于个人原因需要在一台电脑上部署两个Hexo博客,本来以为挺简单,没想到问题重重,首先是一个GitHub账号只能搭建一个Hexo博客,因此就需要使用其他GitHub账号;其次是一台电脑绑定两个GitHub账号,则需要两对公钥,在处理第二个问题时遇到的问题比较多,因为对这方面一窍不通,还是小白,所以折腾了一下午才解决,网上好多教程我都看不懂,觉得不(自)够(己)详(太)细(笨),因此详细记录一下","text":"由于个人原因需要在一台电脑上部署两个Hexo博客,本来以为挺简单,没想到问题重重,首先是一个GitHub账号只能搭建一个Hexo博客,因此就需要使用其他GitHub账号;其次是一台电脑绑定两个GitHub账号,则需要两对公钥,在处理第二个问题时遇到的问题比较多,因为对这方面一窍不通,还是小白,所以折腾了一下午才解决,网上好多教程我都看不懂,觉得不(自)够(己)详(太)细(笨),因此详细记录一下 原理分析: SSH的公钥是GitHub作为本地仓库和远程仓库连接的唯一标识,一个公钥只能对应一个GitHub账户,如果将一个相同的公钥上传到不同的GitHub账户,GitHub则无法做出辨识,进而导致错误 一台电脑,可以生成多对公私钥,可以通过配置,将不同的公钥上传到不同的GitHub账号,那么就不存在单个公钥绑定多个GitHub账号的情况存在了 相关问题报错: 同一台电脑部署第二个Hexo博客执行hexo g -d时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx. 添加新的 SSH 密钥 到 SSH agent 执行ssh-add xxx时报错:Could not open a connection to your authentication agent. 单独设置用户名/邮箱时报错:fatal: not in a git directory 以下是详细过程:前提:假设你的第二个博客相关配置操作已经顺利完成,但使用hexo g -d命令部署到 GitHub 上时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx. - 查看当前密钥首先我们打开终端输入ls ~/.ssh/可以查看当前已有的密钥,显示id_rsa 与 id_rsa_pub说明已经有一对密钥 - 创建新的密钥首先使用以下命令进入 SSH根目录下:1cd ~/.ssh/ 方法一直接使用以下命令创建新密钥,然后两次回车即可:1ssh-keygen -t rsa -f ~/.ssh/这里是新密钥名称 -C \"这里是你的邮箱\" 注意区别新密钥名称和旧密钥名称,不要相同!!! 方法二使用下面命令行创建新密钥:1ssh-keygen -t rsa -C \"这里是你的邮箱\" 回车后会出现:12Generating public/private rsa key pair. Enter file in which to save the key (/c/Users/you/.ssh/id_rsa): 注意此时需要你输入新密钥的名称,同样要注意区别新密钥名称和旧密钥名称,不要相同!!!之后再两次回车,新密钥创建完毕! - 配置config查看你的.ssh/根路径下, 有没有config文件,( 比如我的路径为C:\\Users\\Lenovo.ssh)没有则使用以下命令创建一个config文件:1touch config 用记事本或者其他工具打开config文件(注意config文件是没有任何后缀名的),写入以下配置: 1234567891011#第一个账号,默认使用的账号,不用做任何更改Host github.com HostName github.com User git IdentityFile ~/.ssh/id_rsa #第二个新账号,#\"xxxxxx\"为前缀名,可以任意设置,要记住,后面需要用到Host xxxxxx.github.com HostName github.com User git IdentityFile ~/.ssh/这里是你创建的新密钥的名称 - 设置新GitHub账户SSH key输入以下命令复制你创建的公钥:1clip < ~/.ssh/这里是你创建的新密钥的名称.pub 也可以直接在.ssh目录下找到你创建的新的公钥,文件名为新密钥的名称.pub,(比如我的是trhx_rsa.pub),用记事本打开,复制里面的内容,然后打开你的新GitHub账号主页,依次进入Settings —> SSH and GPG keys —> New SSH key,将刚复制的内容粘贴到Key那里,Title可以随便填,点击Add Key保存。 - 清空本地的 SSH 缓存,添加新的 SSH 密钥 到 SSH agent中使用命令cd ~/.sshcd到.ssh根目录下,依次执行以下命令: 123ssh-add -Dssh-add xxxxxx #旧密钥名称,一般是id_rsassh-add xxxxxx #新创建的密钥名称 如果执行以上命令出现错误:Could not open a connection to your authentication agent.,那么就需要先执行ssh-agent bash,再执行以上命令 - 验证配置是否成功依次执行以下命令,第一个为默认ssh_key验证;第二个为新的ssh_key验证,其中“xxxxxx”为你先前在config文件中的命名12ssh -T git@github.comssh -T git@xxxxxxx.github.com 依次显示以下信息, 则说明配置成功:1Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access. - 取消全局用户名/邮箱配置,单独设置用户名/邮箱执行如下命令,取消全局用户名和邮箱配置(如果已经设置了全局的话): 12git config --global --unset user.namegit config --global --unset user.email 分别进入你的两个Hexo博客.git目录下执行以下命令单独设置用户名/邮箱:12git config user.name \"这里是用户名\"git config user.email \"这里是你的邮箱\" 如果此时报错:fatal: not in a git directory,说明你没有进入.git目录下,具体路径:\\Hexo\\.deploy_git\\.git,.git目录是隐藏的,需要你设置隐藏目录可见 执行以下命令可以查看设置是否成功1git config --list - hexo 配置文件修改git地址打开你的第二个博客Hexo目录下的_config.yml文件,找到deploy关键字,写入以下配置并保存:1234deploy: type: git repository: git@xxxxxx.github.com:你的用户名/你的用户名.github.io.git branch: master 比如我的配置:1234deploy: type: git repository: git@love109.github.com:love109/love109.github.io.git branch: master 大功告成,再次执行hexo g -d就能成功将新的博客部署到 Github 上了","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"Github","slug":"Github","permalink":"https://www.itrhx.com/tags/Github/"}]},{"title":"Python3 基础学习笔记 C09","slug":"A15-Python3-basic-C09","date":"2018-11-15T16:46:13.649Z","updated":"2019-09-24T12:45:51.693Z","comments":true,"path":"2018/11/16/A15-Python3-basic-C09/","link":"","permalink":"https://www.itrhx.com/2018/11/16/A15-Python3-basic-C09/","excerpt":"Python3 基础学习笔记第九章 —— 【文件和异常】","text":"Python3 基础学习笔记第九章 —— 【文件和异常】 - 9.1 从文件中读取数据 - 9.1.1 读取整个文件 有一个文件,包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:12345Circumference rate.txt----------3.1415926535 8979323846 2643383279 以下两个程序将打开并读取这个文件,再将其内容显示到屏幕上:12345#file_reader.pywith open('Circumference rate.txt') as file_object: contents = file_object.read() print(contents) 12345#file_reader2.pycontents = open ('Circumference rate.txt')print(contents.read())contents.close() 函数open()接受一个参数:要打开的文件的名称,Python在当前执行的文件所在的目录中查找指定的文件;关键字with在不再需要访问文件后将其关闭;也可以调用open()和close()来打开和关闭文件,如果使用这种方法,当程序存在bug时,close()语句未执行,文件将不会被关闭;方法read()将读取这个文件的全部内容,并将其作为一个长长的字符串储存在变量contents中,通过打印contents的值,就可以将这个文本文件的全部内容打印出来:1233.1415926535 8979323846 2643383279 输出结果末尾有一空行,这是因为read()到达末尾时返回一个空字符串,而将这个空字符串显示出来就是一个空行,如果要删除末尾的空行,可在print语句中使用rstrip():12345#file_reader.pywith open('Circumference rate.txt') as file_object: contents = file_object.read() print(contents.rstrip()) 输出结果如下:1233.1415926535 8979323846 2643383279 - 9.1.2 文件路径 相对文件路径:假定程序文件位于python_work文件夹中,程序文件操作的文本文件位于python_work文件夹的子文件夹text_files中,此时可以使用相对文件路径来打开该文本文件,相对文件路径让Python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的 在Linux和OS X中,相对路径类似于如下:1with open('text_files/filename.txt') as file_object: 在Windows系统中,文件路径中使用反斜杠(\\)而不是斜杠(/):1with open('text_files\\filename.txt') as file_object: 绝对文件路径:不用关心当前运行的程序储存在什么地方,直接将文件在计算机中的准确位置告诉Python,这称为绝对文件路径,绝对路径通常比相对路径更长,因此将其储存在一个变量中,再将变量传递给open()会有所帮助 在Linux和OS X中,绝对路径类似于如下:12file_path = '/home/ehmatthes/other_files/text_files/filename.txt'with open(file_path) as file_object: 在Windows系统中,绝对路径类似于如下:12file_path = 'C:\\Users\\ehmatthes\\other_files\\text_files\\filename.txt'with open(file_path) as file_object: - 9.1.3 逐行读取 要以每次一行的方式检查文件,可对文件对象使用for循环:123456#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: for line in file_object: print(line) 在文件中每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,一个来自print语句,输出结果如下:123453.1415926535 8979323846 2643383279 要消除这些多余的空白行,可以使用rstrip():123456#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: for line in file_object: print(line.rstrip()) 输出结果如下:1233.1415926535 8979323846 2643383279 - 9.1.4 创建一个包含文件各行内容的列表 使用关键字with时,open()返回的文件对象只在with代码块内可用,如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行储存在一个列表当中,并在with代码块外使用该列表:12345678#file_reader.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines() for line in lines: print(line.rstrip()) 输出结果与文件内容完全一致 - 9.1.5 使用文件的内容 创建一个字符串,它包含文件中储存的所有数字,且没有任何空格:123456789101112#pi_string.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines()pi_string = ''for line in lines: pi_string += line.rstrip() print(pi_string)print(len(pi_string)) 打印该字符串以及其长度:123.1415926535 8979323846 264338327936 由于原文件每行左边都有空格,我们可以使用strip()而不是rstrip()来删除它:123456789101112#pi_string.pyfilename = 'Circumference rate.txt'with open(filename) as file_object: lines = file_object.readlines()pi_string = ''for line in lines: pi_string += line.strip() print(pi_string)print(len(pi_string)) 输出结果如下:123.14159265358979323846264338327932 Python中有三个去除头尾字符、空白符的函数,它们依次为: strip:用来去除头尾字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格) lstrip:用来去除开头字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格) rstrip:用来去除结尾字符、空白符(包括\\n、\\r、\\t、’ ‘,即:换行、回车、制表符、空格)注意:这些函数都只会删除头和尾的字符,中间的不会删除。用法分别为:string.strip([chars])string.lstrip([chars])string.rstrip([chars])参数chars是可选的,当chars为空,默认删除string头尾的空白符(包括\\n、\\r、\\t、’ ‘)当chars不为空时,函数会被chars解成一个个的字符,然后将这些字符去掉它返回的是去除头尾字符(或空白符)的string副本,string本身不会发生改变 - 9.2 写入文件 将一条简单的消息储存到文件中:12345#write_message.pyfilename = 'programming.txt'with open(filename,'w') as file_object: file_object.write(\"I love programming!\") 调用open()时提供了两个实参,第一个实参也是要打开文件的名称,第二个实参(’w’)告诉Python,我们要以写入模式打开这个文件,打开文件时,可指定读取模式(’r’)、写入模式(’w’)、附加模式(’a’)或者让我们能够读取和写入文件的模式(’r+’),如果省略模式实参,则默认以只读模式打开文件 附表:Python读写文件各种模式区别 模式 可做操作 若文件不存在 是否覆盖 r 打开一个文件用于只读 报错 - rb 以二进制格式打开一个文件用于只读 报错 - r+ 打开一个文件用于读和写 报错 是 rb+ 以二进制格式打开一个文件用于读和写 报错 是 w 打开一个文件用于只写 创建 是 wb 以二进制格式打开一个文件只用于只写 创建 是 w+ 打开一个文件用于读和写 创建 是 wb+ 以二进制格式打开一个文件用于读和写 创建 是 a 打开一个文件用于追加 创建 否,追加写 ab 以二进制格式打开一个文件用于追加 创建 否,追加写 a+ 打开一个文件用于读和写 创建 否,追加写 ab+ 以二进制格式打开一个文件用于追加 创建 否,追加写 - 9.3 使用 try-except 代码块处理异常 当我们尝试将一个数字除以0时,会发生ZeroDivisionError异常:12345>>> print(5/0)Traceback (most recent call last): File \"<pyshell#0>\", line 1, in <module> print(5/0)ZeroDivisionError: division by zero 此时我们可以编写一个try-except代码块来处理该异常:1234try: print(5/0)except ZeroDivisionError: print(\"You can't divide by zero!\") 当我们运行该程序时,会出现提示:1You can't divide by zero! 在try-except代码块中加入else,编写一个只执行除法运算的简单计算器:12345678910111213141516print(\"Give me two numbers,and I'll divide them.\")print(\"Enter 'q' to quit.\")while True: first_number = input(\"\\nFirst number:\") if first_number == 'q': break second_number = input(\"\\nSecond number:\") if second_number == 'q': break try: answer = int(first_number)/int(second_number) except ZeroDivisionError: print(\"You can't divide by 0!\") else: print(answer) 运行程序:1234567891011121314Give me two numbers,and I'll divide them.Enter 'q' to quit.First number:45Second number:0You can't divide by 0!First number:36Second number:84.5First number:q 若不加入try-except代码块,我们在输入0时,程序就会出现异常而崩溃,而try-except代码块很好的解决了这种问题,而且还起到了提示的作用,同样的,try-except代码块也可以处理其他异常,如FileNotFoundError等 - 9.4 储存数据 - 9.4.1 使用 json.dump() 和 json.load() 模块json能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据;编写一个储存一组数字的简短程序,再编写一个将这些数字读取到内存中的程序,第一个程序将使用 json.dump()来储存这组数据,而第二个程序将使用 json.load()。函数 json.dump()接受两个实参:要储存的数据以及可用于储存数据的文件对象:123456789#number_writer.pyimport jsonnumbers = [2,3,5,7,11,13]filename = 'numbers.json'with open(filename,'w') as f_obj: json.dump(numbers,f_obj) 先导入模块json,再创建一个数字列表, 通常用文件扩展名.json来指出文件储存的数据为JSON格式,然后以写入模式打开该文件,使用函数json.dump()将数字列表储存到文件numbers.json中,打开该文件,数据的储存格式与Python一样:1[2, 3, 5, 7, 11, 13] 再编写一个程序,使用json.load()将这个列表读取到内存中:12345678#number_reader.pyimport jsonfilename = 'numbers.json'with open(filename) as f_obj: numbers = json.load(f_obj)print(numbers) 输出结果与number_writer.py中创建的数字列表相同:1[2, 3, 5, 7, 11, 13] 进阶:在同一个程序中使用 json.dump() 和 json.load():创建文件username.json储存用户名,从该文件中获取用户名,如果这个文件不存在,就在except代码块中提示用户输入用户名,并将其储存在username.json中:1234567891011121314151617#remember_me.pyimport json#如果以前储存了用户名,就加载它#否则就提示用户输入用户名并储存它filename = 'numbers.json'try: with open(filename) as f_obj: username = json.load(f_obj)except FileNotFoundError: username = input(\"What's your name?\") with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\")else: print(\"Welcome back, \" + username + \"!\") 以前没有储存用户名,第一次运行程序:12What's your name?TRHXWe'll remember you when you come back, TRHX! 再次运行程序:1Welcome back, TRHX! - 9.4.2 重构 代码能够正确运行,但可以做进一步的改进——将代码划分为一系列完成具体工作的函数,这样的过程称为重构,重构让代码更清晰、更易于理解、更容易扩展重构remember_me.py,将大部分逻辑放到一个或者多个函数中:12345678910111213141516171819#remember_me.pyimport jsondef greet_user(): #问候用户,并指出其名字 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: username = input(\"What's your name?\") with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\") else: print(\"Welcome back, \" + username + \"!\")greet_user() 重构greet_user(),让它不执行这么多任务——将获取储存的用户名的代码移到另一个函数中:12345678910111213141516171819202122232425262728#remember_me.pyimport jsondef get_stored_username(): #如果储存了用户名,就获取它 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return usernamedef greet_user(): #问候用户,并指出其名字 username = get_stored_username() if username: print(\"Welcome back, \" + username + \"!\") else: username = input(\"What's your name?\") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) print(\"We'll remember you when you come back, \" + username + \"!\") greet_user() 将greet_user()中的另一个代码块提取出来:将没有储存用户名时提示用户输入的代码放在一个独立的函数中:12345678910111213141516171819202122232425262728293031323334#remember_me.pyimport jsondef get_stored_username(): #如果储存了用户名,就获取它 filename = 'numbers.json' try: with open(filename) as f_obj: username = json.load(f_obj) except FileNotFoundError: return None else: return usernamedef get_new_username(): #提示输入用户名 username = input(\"What's your name?\") filename = 'username.json' with open(filename,'w') as f_obj: json.dump(username,f_obj) return username def greet_user(): #问候用户,并指出其名字 username = get_stored_username() if username: print(\"Welcome back, \" + username + \"!\") else: username = get_new_username() print(\"We'll remember you when you come back, \" + username + \"!\") greet_user() 最终版本实现了每个函数只负责单一而清晰的任务,我们在编写程序时也要像这样,要写出清晰而易于维护和扩展的代码","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"文件","slug":"文件","permalink":"https://www.itrhx.com/tags/文件/"},{"name":"异常","slug":"异常","permalink":"https://www.itrhx.com/tags/异常/"}]},{"title":"Python3 基础学习笔记 C08","slug":"A14-Python3-basic-C08","date":"2018-11-10T16:10:47.892Z","updated":"2019-09-24T12:45:48.188Z","comments":true,"path":"2018/11/11/A14-Python3-basic-C08/","link":"","permalink":"https://www.itrhx.com/2018/11/11/A14-Python3-basic-C08/","excerpt":"Python3 基础学习笔记第八章 —— 【类】","text":"Python3 基础学习笔记第八章 —— 【类】 - 8.1 创建类和使用类 创建一个表示小狗的简单类Dog,根据Dog类创建的每个实例都将储存名字和年龄,赋予每条小狗蹲下(sit())和打滚(roll_over())的能力: 1234567891011121314class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\") 方法init():类中的函数称为方法,本例中方法init()是一个特殊的方法,每当我们根据Dog类创建新实例时,Python都会自动运行它,在方法的名称中,开头和结尾各有两个下划线,这是一种约定,避免Python默认方法与普通方法发生名称冲突,例子中将方法init()定义成了包含三个形参:self、name和age,在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面,Python调用方法init()来创建Dog实例时,将自动传入实参self,每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法,我们创建Dog实例时,Python将调用Dog类的方法init(),我们将通过实参向Dog()传递名字和年龄;self会自动传递,因此我们不需要传递它,每当我们根据Dog类创建实例时,都只需要给最后两个形参(name和age)提供值;定义的两个变量都有前缀self,以self为前缀的变量都可以供类中的所有方法使用,还可以通过类的任何实例来访问这些变量。self.name = name 获取储存在形参name中的值,并将其储存到变量name中,然后该变量被关联到当前创建的实例。self.age = age 的作用与此类似,像这样可通过实例访问的变量称为属性;Dog还定义了另外两种方法:sit() 和 roll_over() ,由于这些方法不需要额外的信息,如名字和年龄,因此它们只有一个形参self 在Python 2.7中创建类时,需要在括号内包含单词object:12class ClassName(object): ---snip--- - 8.2 根据类创建实例访问属性:创建一个表示特定小狗的实例: 123456789101112131415161718class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)print(\"My dog's name is \" + my_dog.name.title() + \".\")print(\"My dog is \" + str(my_dog.age) + \" years old.\") 让Python创建一条名字为’willie’,年龄为6的小狗,Python使用实参’willie’和6调用Dog类中的方法init()。方法init()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age;在访问实例的属性时,可使用句点表示法,比如该例子中的 my_dog.name;最终程序输出结果如下: 12My dog's name is Willie.My dog is 6 years old. 调用方法:根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法:123456789101112131415161718class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)my_dog.sit()my_dog.roll_over() 输出结果如下:12Willie is now sitting.Willie rolled over! 创建多个实例:可按需求根据类创建任意数量的实例:12345678910111213141516171819202122232425class Dog(): def __init__(self,name,age): #初始化属性name和age self.name = name self.age = age def sit(self): #模拟小狗被命令时蹲下 print(self.name.title() + \" is now sitting.\") def roll_over(self): #模拟小狗被命令时打滚 print(self.name.title() + \" rolled over!\")my_dog = Dog('willie',6)your_dog = Dog('lucy',8)print(\"My dog's name is \" + my_dog.name.title() + \".\")print(\"My dog is \" + str(my_dog.age) + \" years old.\")my_dog.sit()print(\"\\nYour dog's name is \" + your_dog.name.title() + \".\")print(\"Your dog is \" + str(your_dog.age) + \" years old.\")your_dog.roll_over() 输出结果如下:1234567My dog's name is Willie.My dog is 6 years old.Willie is now sitting.Your dog's name is Lucy.Your dog is 8 years old.Lucy rolled over! - 8.3 使用类和实例 创建一个表示汽车的类,其中储存了有关汽车的信息,还有一个汇总这些信息的方法:12345678910111213class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name()) 输出结果如下:12018 Audi A9 - 8.3.1 给属性指定默认值 类中的每个属性都必须有初始值,如果我们设置了默认值,就无需包含为它提供初始值的形参,下面为8.3的例子添加一个 odometer_reading 的属性,其初值是0,添加一个 odometer_reading() 方法,用于读取汽车的里程表: 123456789101112131415161718 class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\")my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.read_odomter() 输出结果如下:122018 Audi A9This car has 0 miles on it. - 8.3.2 修改属性的值 可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值) 直接修改属性的值:要修改属性的值,最简单的方法就是通过实例直接访问它,将8.3.1中的例子第7行代码 self.odometer_reading = 0 改为 self.odometer_reading = 66,输出结果如下:12 2018 Audi A9This car has 66 miles on it. 通过方法修改属性的值:1234567891011121314151617181920212223class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): self.odometer_reading = mileage my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(66)my_new_car.read_odomter() 对Car类所做的唯一修改就是在第17、18行添加了方法 update_odometer(),这个方法接受一个里程值,并将其储存到 self.odometer_reading 中,在倒数第二行,调用了 update_odometer(),并向它提供了一个实参(该实参对应于方法定义中的形参mileage),它将里程数设置为66,而方法 read_odomter() 打印该读数:122018 Audi A9This car has 66 miles on it. 可对方法 update_odometer() 进行扩展,使其能够在修改里程表读数时做一些额外的工作,添加一些逻辑,禁止任何人将里程表读数往回调:1234567891011121314151617181920212223242526class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 50 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(33)my_new_car.read_odomter() 修改 self.odometer_reading 的默认值为50,当我们再次尝试修改其值为33时,由于小于原来的里程,因此无法修改: 1232018 Audi A9You can't roll back an odometer!This car has 50 miles on it. 通过方法对属性的值进行递增:有时候需要将属性值递增到特定的量,而不是将其设置为全新的值,假设我们购买了一辆二手车,从购买到登记期间增加了100英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:123456789101112131415161718192021222324252627282930313233class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self,mileage): if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles my_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.update_odometer(6600)my_new_car.read_odomter()my_new_car.increment_odometer(100)my_new_car.read_odomter() 输出结果如下:1232018 Audi A9This car has 6600 miles on it.This car has 6700 miles on it. - 8.4 继承 编写类时,并非总是要从空白开始,如果要编写的类是另一个现成类的特殊版本,可使用继承,一个类继承另一个类时,它自动获得另一个类的所有属性和方法;原有的类称为父类,而新类称为子类,子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法;继承的通用语法大致如下: 12345678class ClassName1(object): def __init__(self,name1,name2,name3): --snip--class ClassName2(ClassName1): def __init__(self,name1,name2,name3): super().__init__(name1,name2,name3) --snip-- - 8.4.1 子类的方法init() 1234567891011121314151617181920 class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class ElectricCar(Car): #电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性 super().__init__(make,model,year)my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name()) 创建子类时,父类必须包含在当前文件中,且位于子类前面,定义了子类 ElectricCar,定义子类时,必须在括号内指定父类名称,方法 __init__()接受创建Car实例所需信息,super() 是一个特殊的函数,帮助Python将父类和子类关联起来,让Python调用 ElectricCar 的父类的方法 __init__(),让 ElectricCar 实例包含父类的所有属性,父类也称为超类(superclass),程序输出结果如下:12016 Tesla Model S - 8.4.2 Python 2.7 中的继承 在Python 2.7中,ElectricCar类的定义类似于下面这样:12345678class Car(object): def __init__(self,make,model,year): --snip--class ElectricCar(Car): def __init__(self,make,model,year): super(ElectricCar,self).__init__(make,model,year) --snip-- - 8.4.3 给子类定义属性和方法 让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法,下面添加一个电动车特有的属性(battery),以及一个描述该属性的方法: 1234567891011121314151617181920212223class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery_size = 80 def describe_battery(self): print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\")my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.describe_battery() 输出结果如下:122016 Tesla Model SThis car has a 80-KWh battery. - 8.4.4 重写父类的方法 要重写父类的方法,只需要在子类中定义一个与要重写的父类方法同名的方法即可,这样,Python将不会考虑这个父类的方法,而只关心在子类中定义的相应方法,假设Car类有一个名为 fill_gas_tank() 的方法,对于电动车来说毫无意义,因此可以重写它:12345class ElectricCar(Car): --snip-- def fill_gas_tank(self): print(\"This car doesn't need a gas tank!\") - 8.4.5 将实例用作属性 123456789101112131415161718192021222324252627282930class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\")class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery = Battery()my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.battery.describe_battery() 输出结果如下: 122016 Tesla Model SThis car has a 70-KWh battery. 看起来似乎做了多余的工作,但现在我们可以对电瓶添加更多的描述,而且不会导致 ElectricCar 类混乱不堪,下面再给Battery添加一个方法,使其能够根据电瓶容量报告汽车的续航里程:1234567891011121314151617181920212223242526272829303132333435363738394041class Car(): def __init__(self,make,model,year): self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title()class Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message) class ElectricCar(Car): def __init__(self,make,model,year): super().__init__(make,model,year) self.battery = Battery()my_new_car = ElectricCar('tesla','model s','2016')print(my_new_car.get_descriptive_name())my_new_car.battery.describe_battery()my_new_car.battery.get_range() 输出结果如下:1232016 Tesla Model SThis car has a 70-KWh battery.This car can go approximately 240 miles on a full charge. - 8.5 导入类 Python允许将类储存在模块中,然后在主程序中导入所需的模块 - 8.5.1 导入单个类 12345678910111213141516171819202122232425262728293031#car.py#一个用于表示汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odomter(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles 创建另一个文件——my_car.py,在其中导入Car类并创建其实例:123456789#my_car.pyfrom car import Carmy_new_car = Car('audi','a9','2018')print(my_new_car.get_descriptive_name())my_new_car.odometer_reading = 23my_new_car.read_odometer() import语句让Python打开模块car,并导入其中的Car类,输出结果如下: 122018 Audi A9This car has 23 miles on it. - 8.5.2 在一个模块中储存多个类 将类Battery和ElectricCar都加入到模块car.py中: 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758#car.py#一组用于表示燃油汽车和电动汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odometer(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += milesclass Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message)class ElectricCar(Car): #模拟电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性,再初始化电动车特有的属性 super().__init__(make,model,year) self.battery = Battery() 新建一个my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动车:123456789#my_electric_car.pyfrom car import ElectricCarmy_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name())my_tesla.battery.describe_battery()my_tesla.battery.get_range() 输出结果如下:1232016 Tesla Model SThis car has a 70-KWh battery.This car can go approximately 240 miles on a full charge. - 8.5.3 从一个模块中导入多个类 可根据需要在程序文件中导入任意数量的类,假如我们要在同一个程序中创建普通汽车和电动汽车,就需要将类Car和ElectricCar类都导入,多个类之间用逗号进行分隔: 123456789 #my_car.pyfrom car import Car,ElectricCarmy_audi = Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 输出结果如下:122018 Audi A92016 Tesla Model S - 8.5.4 导入整个模块 导入整个模块后,需要使用句点表示法访问需要的类:123456789#my_car.pyimport carmy_audi = car.Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = car.ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 我们导入了整个car模块,需要使用语法 module_name.class_name 访问需要的类,程序输出结果与8.5.3一致:122018 Audi A92016 Tesla Model S - 8.5.5 导入模块中的所有类 要导入模块中的所有类,可使用以下语法:1from module_name import * 这种导入方法是不推荐的,没有明确指出你使用了模块中的哪些类,还可能引发名称方面的困惑,需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类 - 8.5.6 在一个模块中导入另一个模块 有时候需要将类分散到多个模块当中,以免模块太大,或者在同一个模块中储存不相关的类,将类储存在多个模块中时,一个模块中的类可能会依赖于另一个模块中的类,这种情况下,我们可以在前一个模块中导入必要的类,以下例子中,将Car类储存在一个模块当中,并将ElectricCar和Battery类储存在另一个模块当中,将第二个模块命名为electric_car.py,并将ElectricCar和Battery类复制到这个模块中:12345678910111213141516171819202122232425262728293031#electric_car.py#一组可用于表示电动汽车的类from car import Carclass Battery(): #一次模拟电动车电瓶的简单尝试 def __init__(self,battery_size=70): #初始化电瓶的属性 self.battery_size = battery_size def describe_battery(self): #打印一条描述电瓶容量的消息 print(\"This car has a \" + str(self.battery_size) + \"-KWh battery.\") def get_range(self): #打印一条消息,指出电瓶的续航里程 if self.battery_size == 70: range = 240 elif self.battery_size == 90: range = 280 message = \"This car can go approximately \" + str(range) message += \" miles on a full charge.\" print(message)class ElectricCar(Car): #模拟电动车的独特之处 def __init__(self,make,model,year): #初始化父类的属性,再初始化电动车特有的属性 super().__init__(make,model,year) self.battery = Battery() 12345678910111213141516171819202122232425262728293031#car.py#一个可用于表示汽车的类class Car(): def __init__(self,make,model,year): #初始化描述汽车的属性 self.make = make self.model = model self.year = year self.odometer_reading = 0 def get_descriptive_name(self): #返回整洁的描述性名称 long_name = str(self.year) + ' ' + self.make + ' ' +self.model return long_name.title() def read_odometer(self): #打印一条消息,指出汽车的里程 print(\"This car has \" + str(self.odometer_reading) + \" miles on it.\") def update_odometer(self): #将里程表读数设置为指定的值,拒绝将里程表往回拨 if mileage >= self.odometer_reading: self.odometer_reading = mileage else: print(\"You can't roll back an odometer!\") def increment_odometer(self,miles): #将里程表读数增加指定的量 self.odometer_reading += miles 现在可以分别从每个模块中导入类:12345678910#my_car.pyfrom car import Carfrom electric_car import ElectricCarmy_audi = Car('audi','a9','2018')print(my_audi.get_descriptive_name())my_tesla = ElectricCar('tesla','model s','2016')print(my_tesla.get_descriptive_name()) 输出结果如下:122018 Audi A92016 Tesla Model S - 8.6 Python标准库 Python标准库是一组模块,安装的Python都包含它,我们可以使用标准库中的任何函数和类,只需要在程序的开头包含一条简单的import语句,下面以模块collections中的一个类——OrderedDict(创建字典并记录其中的键-值对的添加顺序)为例:1234567891011121314#favorite_languages.pyfrom collections import OrderedDictfavorite_languages = OrderedDict()favorite_languages ['jen'] = 'python'favorite_languages ['sarah'] = 'c'favorite_languages ['edward'] = 'java'favorite_languages ['anly'] = 'python'for name,language in favorite_languages.items(): print(name.title() + \"'s favorite languages is \" + language.title() + \".\") 输出结果如下:1234Jen's favorite languages is Python.Sarah's favorite languages is C.Edward's favorite languages is Java.Anly's favorite languages is Python.","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"类","slug":"类","permalink":"https://www.itrhx.com/tags/类/"},{"name":"继承","slug":"继承","permalink":"https://www.itrhx.com/tags/继承/"}]},{"title":"Python3 基础学习笔记 C07","slug":"A13-Python3-basic-C07","date":"2018-11-03T14:21:47.735Z","updated":"2019-09-24T12:45:42.627Z","comments":true,"path":"2018/11/03/A13-Python3-basic-C07/","link":"","permalink":"https://www.itrhx.com/2018/11/03/A13-Python3-basic-C07/","excerpt":"Python3 基础学习笔记第七章 —— 【函数】","text":"Python3 基础学习笔记第七章 —— 【函数】 - 7.1 定义函数 一个简单的函数,命名为 example(),其中,关键字 def 来告诉Python我们要定义一个函数,这就是函数定义 123def example(): print(\"Hello world!\")example() 输出结果如下: 1Hello world! - 7.1.1 向函数传递信息 在函数定义 def example() 的括号中添加 username,可以让函数接受我们给 username 指定的任何值,在调用函数时给 username 指定一个值,调用 example() 时,可将一个名字传递给它: 123def example(username): print(\"Hello , \" + username + '!')example('TRHX') 输出结果如下: 1Hello , TRHX! - 7.1.2 实参和形参 在 7.1.1 的例子中,函数 example() 的定义中,变量 username 是一个形参——函数完成其工作所需的一项信息,在代码 example(‘TRHX’) 中,值’TRHX’是一个实参,实参是调用函数时传递给函数的信息,调用函数时,将要让函数使用的信息放在括号内。在 example(‘TRHX’) 中,将实参 ‘TRHX’ 传递给了函数 example,这个值被储存在形参 username 中 - 7.2 传递实参 鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参,这要求实参的顺序与形参的顺序相同;也可以使用关键字实参,其中每个实参都由变量和值组成;还可以使用列表和字典 - 7.2.1 位置实参 调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的方法是基于实参的顺序,这种关联方式被称为位置实参 1234def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet('hamster' , 'harry') 输出结果如下: 12I have a hamster.My hamster's name is Harry. 调用函数多次:我们可以根据需要调用函数任意次,要再描述一个宠物,只需要再次调用 123456describe_pet() 即可def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet('hamster' , 'harry')describe_pet('dog' , 'willi') 输出结果如下: 1234I have a hamster.Myhamster's name is Harry.I have a dog.My dog's name is Willi. - 7.2.2 关键字实参 关键字实参是传递给函数的名称-值对。直接在实参中将名称和值关联起来,不用考虑函数调用中的实参顺序 12345def describe_pet(animal_type , pet_name): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet(animal_type = 'hamster' , pet_name = 'harry')describe_pet(pet_name = 'willi' , animal_type = 'dog' ) 输出结果如下: 1234I have a hamster.Myhamster's name is Harry.I have a dog.My dog's name is Willi. - 7.2.3 默认值 编写函数时,可给每个形参指定默认值,在调用函数中给形参提供了实参时,Python将使用指定的实参值,否则将使用形参的默认值 1234def describe_pet(pet_name , animal_type = 'dog'): print(\"I have a \" + animal_type + \".\") print(\"My \" + animal_type + \"'s name is \" + pet_name.title() + \".\")describe_pet(pet_name = 'willi') 输出结果如下: 12I have a dog.My dog's name is Willi. 在这个函数定义中,修改了形参的排列顺序,由于给 animal_type 指定了默认值,无需通过实参来指定动物类型,因此在函数调用中只包含一个实参——宠物的名字,然而Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参,这就是需要将 pet_name 放在形参列表开头的原因所在 注意:使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的形参,这让Python依然能够准确地解读位置实参 - 7.3 返回值 函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值,函数返回的值被称为返回值,在函数中,可使用 return 语句将值返回到函数调用的代码行 - 7.3.1 返回简单值 12345def name(first_name , last_name): full_name = first_name + ' ' + last_name return full_name.title()student = name('jimi' , 'hendrix')print(student) 输出结果如下: 1Jimi Hendrix - 7.3.2 让实参变成可选的 对 7.3.1 的例子进行改进,扩展函数 name,使其还能够处理中间名: 12345def name(first_name , middle_name , last_name): full_name = first_name + ' ' + middle_name + ' ' + last_name return full_name.title()student = name('jimi' , 'lee' , 'hendrix')print(student) 输出结果如下: 1Jimi Lee Hendrix 然而,如果一个人没有中间名,那么在调用这个函数时就会出错,为了让中间名变成可选的,可以给实参 middle_name 指定一个默认值——空字符串,并在用户没有提供中间名时不使用这个实参,注意需要将 middle_name 移到形参列表的末尾: 12345678910def name(first_name , last_name , middle_name = ' '): if middle_name: full_name = first_name + ' ' + middle_name + ' ' + last_name else: full_name = first_name + ' ' + last_name return full_name.title()student = name('jimi' , 'hendrix')print(student)student = name('jimi' , 'hendrix' , 'lee' )print(student) 输出结果如下: 12Jimi HendrixJimi Lee Hendrix - 7.3.3 返回字典 函数可返回任何类型的值,包括列表和字典等较复杂的数据结构: 12345def name(first_name , last_name): full_name = {'first' : first_name , 'last' : last_name} return full_namestudent = name('jimi' , 'hendrix')print(student) 输出结果如下: 1{'first': 'jimi', 'last': 'hendrix'} - 7.3.4 结合使用函数和 while 循环 123456789101112131415def name(first_name , last_name): full_name = first_name + ' ' + last_name return full_namewhile True: print(\"\\nPlease input your name:\") print(\"(Enter 'exit' to quit)\") f_name = input(\"First_name:\") if f_name == 'exit': break l_name = input(\"Last_name:\") if l_name == 'exit': break student = name(f_name , l_name) print(student) print(\"Hello, \" + student.title() + \"!\") 运行程序: 1234567891011Please input your name:(Enter 'exit' to quit)First_name:jimiLast_name:hendrixjimi hendrixHello, Jimi Hendrix!Please input your name:(Enter 'exit' to quit)First_name:exit - 7.4 传递列表 123456def users(names): for name in names: message = \"Hello, \" + name.title() + \"!\" print(message)usernames = ['hannah' , 'tony' , 'margot']users(usernames) 输出结果如下: 123Hello, Hannah!Hello, Tony!Hello, Margot! - 7.4.1 在函数中修改列表 将列表传递给函数后,函数就可以对其进行修改,在函数中对这个列表所做的任何修改都是永久性的 #首先创造一个列表,其中包含一些要打印的设计 12345678910111213141516unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']completed_models = []#模拟打印每个设计,直到没有未打印的设计为止#打印每个设计后,都将其移到列表completed_models中while unprinted_designs: current_design = unprinted_designs.pop() #模拟根据设计制作3D打印模型的过程 print(\"Printing model: \" + current_design) completed_models.append(current_design) #显示打印好的所有模型print(\"\\nThe following models have been printed: \")for completed_model in completed_models: print(completed_model) 输出结果如下: 12345678Printing model: dodecahedronPrinting model: robot pendanntPrinting model: iphone caseThe following models have been printed: dodecahedronrobot pendanntiphone case 编写两个函数重新组织这些代码,每一个函数都做一件具体的工作,输出结果与原程序相同: 123456789101112131415161718192021def print_models(unprinted_designs , completed_models):#模拟打印每个设计,直到没有未打印的设计为止#打印每个设计后,都将其移到列表completed_models中 while unprinted_designs: current_design = unprinted_designs.pop() #模拟根据设计制作3D打印模型的过程 print(\"Printing model: \" + current_design) completed_models.append(current_design)def show_completed_models(completed_models): #显示打印好的所有模型 print(\"\\nThe following models have been printed: \") for completed_model in completed_models: print(completed_model)unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']completed_models = []print_models(unprinted_designs , completed_models)show_completed_models(completed_models) - 7.4.2 禁止函数修改列表 有时候需要禁止函数修改列表,拿 7.4.1 的例子来说,我们打印了所有设计后,也要保留原来的未打印的设计列表,以供备案,但由于我们将所有的设计都移出了 unprinted_designs,这个列表变成了空的,原来的列表没有了,为了解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件,要将列表的副本传递给函数,可以像下面这样做: 1function_name(list_name[:]) 切片表示法 [:] 创建列表的副本,在 7.4.1 的例子中如果不想清空未打印的设计列表,可像下面这样调用 print_models(): 1print_models(unprinted_designs[:] , completed_models) - 7.5 传递任意数量的实参 Python允许函数从调用语句中收集任意数量的实参 1234def make_pizza(*toppings): print(toppings)make_pizza('pepperoni')make_pizza('mushrooms' , 'green peppers' , 'extra cheese') 形参名 *toppings 中的星号让Python创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中,函数体内的print语句通过生成输出来证明Python能够处理使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形,输出结果如下: 12('pepperoni',)('mushrooms', 'green peppers', 'extra cheese') 使用循环语句: 123456def make_pizza(*toppings): print(\"\\nMaking a pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping)make_pizza('pepperoni')make_pizza('mushrooms' , 'green peppers' , 'extra cheese') 输出结果如下: 12345678Making a pizza with the followiing toppings: - pepperoniMaking a pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.5.1 结合使用位置实参和任意数量实参 如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中: 123456def make_pizza(size , *toppings): print(\"\\nMaking a \" + str(size) + \"-inch pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping)make_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') 输出结果如下: 12345678Making a 16-inch pizza with the followiing toppings: - pepperoniMaking a 18-inch pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.5.2 使用任意数量的关键字实参 有时候,需要接受任何数量的实参,但预先我们不知道传递给函数的会是什么样的信息,在这种情况下,可以将函数编写成能够接受任意数量的键-值对——调用语句提供了多少就接受多少: 12345678910def build_profile(first , last , **user_info): #创建一个字典,其中包括我们知道的有关用户的一切 profile = {} profile['first_name'] = first profile['last_name'] = last for key , value in user_info.items(): profile[key] = value return profileuser_profile = build_profile('albert' , 'einstein' , location = 'princeton' , field = 'physics')print(user_profile) 形参 **user_info 中的两个星号让Python创建一个名为 user_info 的空字典,并将收到的所有名称-值对都封装到这个字典中,在这个函数中,可以像访问其他字典那样访问 user_info 中的名字-值对,程序运行结果如下: 1{'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'} - 7.6 将函数储存在模块中 更进一步,我们可以把函数储存在被称为模块的独立文件中,再将模块导入到主程序中,import 语句运行在当前运行的程序文件中使用模块中的代码 - 7.6.1 导入整个模块 要让函数是可导入的,得先创建模块,模块是扩展名为.py的文件,包含要导入到程序中的代码,下面将创建一个包含函数 make_pizza() 的模块 1234567#pizza.pydef make_pizza(size , *toppings): #概述要制作的比萨 print(\"\\nMaking a \" + str(size) + \"-inch pizza with the followiing toppings: \") for topping in toppings: print(\"- \" + topping) 接下来,我们在 pizza.py 所在的目录中创建另一个名为 making_pizzas.py 的文件,在这个文件中导入刚刚创建的模块,在调用 make_pizza() 两次: 12345#making_pizzas.pyimport pizzapizza.make_pizza(16 , 'pepperoni')pizza.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') Python在读取这个文件时,代码行 import pizza 让Python打开文件 pizza.py,并在幕后将其中所有函数都复制到这个程序中,在 making_pizzas.py 中,可以使用 pizza.py 中定义的所有函数,要调用被导入的模块中的函数,可指定导入的模块的名称 pizza 和函数名 make_pizza(),并使用句点分隔它们,最终运行结果与原程序相同: 12345678Making a 16-inch pizza with the followiing toppings: - pepperoniMaking a 18-inch pizza with the followiing toppings: - mushrooms- green peppers- extra cheese - 7.6.2 导入特定的函数 导入模块中特定的函数,可以使用以下语法: 1from module_name import function_name 通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:1from module_name import function_0 , function_1 , function_2 以前面的 making_pizzas.py 为例,如果只想导入要使用的函数,代码类似于下面这样:1234from pizza import make_pizzamake_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.3 使用 as 给函数指定别名 如果要导入的函数名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名,要给函数指定别名,需要在导入它的时候这样做,通用语法为:1from module_name import function_name as fn 同样以前面的 making_pizzas.py 为例:1234from pizza import make_pizza as mpmp(16 , 'pepperoni')mp(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.4 使用 as 给模块指定别名 我们还可以给模块指定别名,通用语法为:1import module_name as mn 同样以前面的 making_pizzas.py 为例:1234import pizza as pp.make_pizza(16 , 'pepperoni')p.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') - 7.6.5 导入模块中的所有函数 导入模块中所有函数的通用语法为:1from module_name import * 同样以前面的 making_pizzas.py 为例:1234from pizza import *make_pizza(16 , 'pepperoni')make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese') import 语句中的星号让Python将模块 pizza 中的每个函数都复制到这个程序中,由于导入了每个函数,可通过名称来调用每个函数,而不需要用句点表示法,然而,如果模块中有函数的名称与项目中的名称相同,就有可能导致意想不到的结果,最佳的做法是,要么只导入我们需要使用的函数,要么导入整个模块并使用句点表示法","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"函数","slug":"函数","permalink":"https://www.itrhx.com/tags/函数/"},{"name":"模块","slug":"模块","permalink":"https://www.itrhx.com/tags/模块/"}]},{"title":"Python3 基础学习笔记 C06","slug":"A12-Python3-basic-C06","date":"2018-10-30T05:42:31.562Z","updated":"2019-09-24T12:45:39.386Z","comments":true,"path":"2018/10/30/A12-Python3-basic-C06/","link":"","permalink":"https://www.itrhx.com/2018/10/30/A12-Python3-basic-C06/","excerpt":"Python3 基础学习笔记第六章 —— 【用户输入和 while 循环】","text":"Python3 基础学习笔记第六章 —— 【用户输入和 while 循环】 - 6.1 函数 input() 的工作原理 函数 input() 让程序暂停运行,等待用户输入一些文本。获取用户输入后,Python将其储存在一个变量当中,以方便你使用;函数 input() 返回为 string 类型 12message = input(\"Please tell me your name:\")print(\"Hello , \" + message + \"!\") 输出结果如下: 12Please tell me your name:anliyHello , anliy! 进阶: 1234message = \"Please tell me your name so that we can personalize the messages you see.\"message += \"\\nWhat's your first name?\"name = input(message)print(\"\\nHello , \" + name + \"!\") 输出结果如下: 1234Please tell me your name so that we can personalize the messages you see.What's your first name?trhxHello , trhx! - 6.1.1 使用 int() 来获取数值输入 使用函数 input() 时,Python会将用户输入解读为字符串: 1234>>> age = input(\"How old are you?\")How old are you?19>>> age'19' 为了解决这个问题,可以使用函数 int() ,它让Python将输入视为数值: 12345>>> age = input(\"How old are you?\")How old are you?19>>> age = int(age)>>> age19 实例: 123456age = input(\"Please tell me your age:\")age = int(age)if age >= 18: print(\"You are old enough to go to the Internet bar!\")else: print(\"You are not old enough to go to Internet bar!\") 输出结果如下: 12Please tell me your age:17You are not old enough to go to Internet bar! - 6.1.2 求模运算符 处理数值信息时,求模运算符(%)是一个很有用的工具,它将两个数相除并返回余数: 12345678>>> 4 % 31>>> 5 % 32>>> 8 % 20>>> 7 % 31 - 6.1.3 在 Python 2.7 中获取输入 如果使用 Python 2.7,应该使用函数 raw_input() 来提示用户输入,这个函数与 Python 3 中的 input() 一样,也将输入解读为字符串;Python 2.7 也包含函数 input(),但它将用户输入解读为Python代码,并尝试运行它们 - 6.2 while 循环 for 循环用于针对集合中的每一个元素的一个代码块,而 while 循环不断地运行,直到指定的条件不满足为止 - 6.2.1 使用 while 循环 一个简单的 while 循环: 1234num = 1while num < 5: print(num) num += 1 输出结果如下: 12341234 - 6.2.2 让用户选择退出循环 123456prompt = \"\\nTell me something, and I will repeat it back to you:\"prompt += \"\\nEnter 'quit' to end the program.\"message = \" \"while message != 'quit': message = input(prompt) print(message) 运行程序: 123456789101112Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.Hello everyone!Hello everyone!Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.Hello again!Hello again!Tell me something, and I will repeat it back to you:Enter 'quit' to end the program.quitquit - 6.2.3 使用标志 在要求很多条件都满足才继续运行的程序中,可以定义一个变量,用于判断整个程序是否处于活动状态,这个变量称为标志 123456789prompt = \"\\nTell me something, and I will repeat it back to you:\"prompt += \"\\nEnter 'quit' to end the program.\"active = Truewhile active: message = input(prompt) if message == 'quit': active = False else: print(message) 运行结果与6.2.2一致 - 6.2.4 使用 break 退出循环 要立即退出 while 循环,不再运行循环中余下的代码,也不管条件测试的结果如何,可使用 break 语句,break 语句用于控制程序流程,可使用它来控制哪些代码将执行,哪些代码不执行 123456789prompt = \"\\nPlease enter the name of a city you have visited:\"prompt += \"\\nEnter 'quit' when you are finished.\"active = Truewhile active: city = input(prompt) if city == 'quit': break else: print(\"I'd love to go to \" + city.title() + \"!\") 运行程序: 1234567891011Please enter the name of a city you have visited:Enter 'quit' when you are finished.ShanghaiI'd love to go to Shanghai!Please enter the name of a city you have visited:Enter 'quit' when you are finished.BeijingI'd love to go to Beijing!Please enter the name of a city you have visited:Enter 'quit' when you are finished.quit 在任何Python循环中都可以使用break语句,例如,可以使用break语句来退出遍历列表或字典 - 6.2.5 在循环中使用 continue 要返回到循环开头,并根据条件测试结果决定是否继续执行循环,可使用 continue 语句,它不像 break 语句那样不再执行余下的代码并退出整个循环,例如,从1到10只打印其中奇数: 123456number =0while number < 10: number += 1 if number % 2 == 0: continue print(number) 输出结果如下:1234513579 - 6.3 使用 while 循环来处理列表和字典 for循环是一种遍历列表的有效方式,但在for循环中不应修改列表,否则将导致Python难以跟踪其中的元素,要在遍历列表的同时对其进行修改,可使用while循环 - 6.3.1 在列表之间移动元素 123456789unconfirmed_users = ['alice' , 'brian' , 'candace']confirmed_users = []while unconfirmed_users: current_user = unconfirmed_users.pop() print(\"Verifying user: \" + current_user.title()) confirmed_users.append(current_user)print(\"\\nThe following users have been confirmed:\")for confirmed_user in confirmed_users: print(confirmed_user.title()) 首先创建一个未验证用户列表,其中包含用户Alice、Brian和Candace,还创建了一个空列表,用于存储已验证的用户,程序中的 while 循环将不断地运行,直到列表 unconfirmed_users 变成空的。在这个循环中,函数pop() 以每次一个的方式从列表 unconfirmed_users 末尾删除未验证的用户。由于Candace位于列表 unconfirmed_users 的末尾,因此其名字将首先被删除、存储到变量 current_user 中并加入到列表 confirmed_users 中。接下来是Brian,然后是Alice 为模拟用户验证过程,我们打印一条验证消息并将用户加入到已验证用户列表中。未验证用户列表越来越短,而已验证用户列表越来越长。未验证用户列表为空后结束循环,再打印已验证用户列表: 12345678Verifying user: CandaceVerifying user: BrianVerifying user: AliceThe following users have been confirmed:CandaceBrianAlice - 6.3.2 删除包含特定值的所有列表元素 可以使用方法 remove() 来删除列表中特定的值,但如果要删除的值在列表中出现了多次,方法 remove() 就不管用了,如果要删除列表中所有包含特定值的元素则可以使用 while 循环: 12345names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']print(names)while 'candace' in names: names.remove('candace')print(names) 输出结果如下: 12['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']['alice', 'alice', 'brian', 'alix', 'heliy'] 使用方法 remove() 做对比: 1234names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']print(names)names.remove('candace')print(names) 输出结果如下: 12['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']['alice', 'alice', 'brian', 'alix', 'candace', 'heliy'] - 6.3.3 使用用户输入来填充字典 12345678910111213141516171819202122responses = {}#设置一个标志,指出调查是否继续polling_active = Truewhile polling_active: #提示输入被调查者的姓名和回答 name = input(\"\\nWhat's your name?\") response = input(\"What kind of fruit do you like?\") #将答卷储存在字典中 responses[name] = response #询问是否还有其他人要参与回答 repeat = input(\"Would you like to let another person respond?(Yes/No)\") if repeat == 'No': polling_active = False#调查结束,显示结果print(\"\\n------ Poll Results ------\")for name , response in responses.items(): print(name + \" like \" + response + \".\") 运行程序: 1234567891011What's your name?TRHXWhat kind of fruit do you like?appleWould you like to let another person respond?(Yes/No)YesWhat's your name?TRHXCCWhat kind of fruit do you like?bananaWould you like to let another person respond?(Yes/No)No------ Poll Results ------TRHX like apple.TRHXCC like banana.","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"input()函数","slug":"input-函数","permalink":"https://www.itrhx.com/tags/input-函数/"},{"name":"while循环","slug":"while循环","permalink":"https://www.itrhx.com/tags/while循环/"}]},{"title":"Python3 基础学习笔记 C05","slug":"A11-Python3-basic-C05","date":"2018-10-27T11:10:10.825Z","updated":"2019-09-24T12:45:35.909Z","comments":true,"path":"2018/10/27/A11-Python3-basic-C05/","link":"","permalink":"https://www.itrhx.com/2018/10/27/A11-Python3-basic-C05/","excerpt":"Python3 基础学习笔记第五章 —— 【字典】","text":"Python3 基础学习笔记第五章 —— 【字典】 - 5.1 一个简单的字典 123fruits = {'apple' : 'red' , 'number' : 5}print(fruits['apple'])print(fruits['number']) 输出结果如下: 12red5 在Python中,字典是一系列键-值对。每个键都与一个值相关联,你可以使用键来访问与之相关联的值。与键相关联的值可以是数字、字符串、列表乃至字典。事实上,可以将任何Python对象用作字典中的值。键-值对是两个相关联的值。在指定键时,Python将返回与之相关联的值。键和值之间用冒号分隔,而键-值对之间用逗号分隔。在字典中,想储存多少个键-值对都可以 - 5.1.1 访问字典中的值 要获取与键相关联的值,可依次指定字典名和放在方括号内的键: 123fruits = {'apple' : 'red' , 'number' : 5}number_fruits = fruits['number']print(\"The number of apple is \" + str(number_fruits) + \"!\") 输出结果如下: 1The number of apple is 5! - 5.1.2 添加键-值对 字典是一种动态结构,可随时在其中添加键-值对。要添加键-值对,可依次指定字典名、用方括号括起来的键和相关联的值 12345fruits = {'apple' : 'red' , 'number1' : 5}print(fruits)fruits['banana'] = 'yellow'fruits['number2'] = 13print(fruits) 输出结果如下: 12{'apple': 'red', 'number1': 5}{'apple': 'red', 'number1': 5, 'banana': 'yellow', 'number2': 13} 注意:键-值对的排列顺序与添加顺序不同。Python不关心键-值对的添加顺序,而只关心键和值之间的关联关系 有时候为了方便也可以先使用一对空的花括号定义一个字典,再分行添加各个键-值对: 1234fruits = {}fruits['banana'] = 'yellow'fruits['number2'] = 13print(fruits) 输出结果如下: 1{'banana': 'yellow', 'number2': 13} - 5.1.3 修改字典中的值 要修改字典中的值,可依次指定字典名、用方括号括起来的键以及与该键相关联的新值 1234fruits = {'color' : 'red'}print(\"The color of the fruits is \" + fruits['color'] + \"!\")fruits['color'] = 'yellow'print(\"The color of the fruits is \" + fruits['color'] + \" now!\") 输出结果如下: 12The color of the fruits is red!The color of the fruits is yellow now! 进阶:对一个能够以不同速度移动的外星人的位置进行跟踪,为此,我们将储存该外星人的当前速度,并据此确定该外星人将向右移动多远: 1234567891011121314alien = {'x_position': 0, 'y_position': 25, 'speed': 'medium'}print(\"Original x-position: \" + str(alien['x_position']))#向右移动外星人,据外星人当前速度决定将其移动多远if alien['speed'] == 'slow': x_increment = 1elif alien['speed'] == 'medium': x_increment = 2else: x_increment = 3#新位置等于老位置加上增量alien['x_position'] = alien['x_position'] + x_incrementprint(\"New x_position: \" + str(alien['x_position'])) 输出结果如下: 12Original x-position: 0New x_position: 2 - 5.1.4 删除键-值对 对于字典中不再需要的信息,可使用del语句将相应的键-值对彻底删除。使用del语句时,必须指定字典名和要删除的键 1234fruits = {'apple' : 'red' , 'number' : 5}print(fruits)del fruits['number']print(fruits) 输出结果如下: 12{'apple': 'red', 'number': 5}{'apple': 'red'} - 5.1.5 由类似对象组成的字典 字典储存的可以是一个对象的多种信息,也可以储存众多对象的同一种信息,例如要调查很多人最喜欢的编程语言: 1234567favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }print(\"Sarah's favorite languages is \" + favorite_languages['sarah'].title() + \"!\") 输出结果如下: 1Sarah's favorite languages is C! - 5.2 遍历字典 - 5.2.1 方法 items() 遍历所有的键-值对 使用for循环来遍历字典:12345678name = { 'username' : 'efermi' , 'first' : 'enrico' , 'last' : 'fermi' , }for key , value in name.items(): print(\"\\nKey: \" + key) print(\"Value: \" + value) 输出结果如下:123456789Key: usernameValue: efermiKey: firstValue: enricoKey: lastValue: fermi for语句的第二部分包含字典和方法items(),它返回一个键-值对列表。接下来,for循环依次将每个键-值对储存到指定的两个变量中 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name, language in favorite_languages.items(): print(name.title() + \"'s favorite language is \" + language.title() + \".\") 输出结果如下: 1234Jen's favorite language is Python.Sarah's favorite language is C.Edward's favorite language is Ruby.Phil's favorite language is Java. - 5.2.2 方法 keys() 遍历字典中所有的键 在不需要使用字典中的值时,方法key()很有用,下面来遍历字典favorite_languages,并将每个被调查者的名字都打印出来: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name in favorite_languages.keys(): print(name.title()) 输出结果如下: 1234JenSarahEdwardPhil 遍历字典时,会默认遍历所有的键,因此,如果将上述代码中的for name in favorite_languages.keys():替换为for name in favorite_languages:输出结果将不变进阶: 1234567891011favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }friends = ['phil', 'sarah']for name in favorite_languages.keys(): print(name.title()) if name in friends: print(\"Hi \" + name + \", I see your favorite languages is \" + favorite_languages[name].title() + \"!\") 输出结果如下: 123456JenSarahHi sarah, I see your favorite languages is C!EdwardPhilHi phil, I see your favorite languages is Java! - 5.2.3 函数 sorted() 按顺序遍历字典中的所有键 字典总是明确地记录键和值之间的关联关系,但获取字典的元素时,获取顺序是不可预测的,要以特定的顺序返回元素,一种办法是在for循环中对返回的键进行排序,为此,可以使用函数sorted()来获得按特定顺序排列的键列表的副本: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for name in sorted(favorite_languages.keys()): print(name.title()) 输出结果如下: 1234EdwardJenPhilSarah - 5.2.4 方法 values() 遍历字典中的所有值 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'java' , }for languages in favorite_languages.values(): print(languages.title()) 输出结果如下: 1234PythonCRubyJava 这种做法提取字典中所有的值,而没有考虑是否重复,为剔除重复项,可使用集合(set),集合类似于列表,但每个元素都必须是独一无二的: 12345678favorite_languages = { 'jen' : 'python' , 'sarah' : 'c' , 'edward' : 'ruby' , 'phil' : 'python' , }for languages in set(favorite_languages.values()): print(languages.title()) 输出结果如下: 123CPythonRuby - 5.3 嵌套 有时候,需要将一系列字典储存在列表中,或将列表作为值储存在字典中,这称为嵌套。可以在列表中嵌套字典、在字典中嵌套列表甚至在字典中嵌套字典 - 5.3.1 字典列表 下面代码创建三个字典,每个字典都表示一个个学生,将这三个字典都放到一个名为students的列表当中,遍历列表将每个学生都打印出来: 123456student_0 = {'name' : 'anily' , 'class' : 2}student_1 = {'name' : 'nikey' , 'class' : 5}student_2 = {'name' : 'heyk' , 'class' : 3}students = [student_0 , student_1 , student_2]for student in students: print(student) 输出结果如下: 123{'name': 'anily', 'class': 2}{'name': 'nikey', 'class': 5}{'name': 'heyk', 'class': 3} 进阶:使用 range() 自动生成三十个外星人: 123456789101112131415#创建一个用于存储外星人的空列表aliens = []#创建三十个绿色的外星人for alien_number in range(30): new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'} aliens.append(new_alien)#显示前五个外星人for alien in aliens[:5]: print(alien)print(\"......\")#显示创建了多少外星人print(\"Total number of aliens: \" + str(len(aliens))) 输出结果如下: 1234567{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}......Total number of aliens: 30 在上述例子中,虽然每个外星人都具有相同特征,但在Python看来,每个外星人都是独立的,我们可以独立地修改每个外星人: 12345678910111213aliens = []for alien_number in range(30): new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'} aliens.append(new_alien)for alien in aliens[0:3]: if alien['color'] == 'green': alien['color'] = 'yellow' alien['points'] = 10 alien['speed'] = 'medium'for alien in aliens[:5]: print(alien)print(\"......\")print(\"Total number of aliens: \" + str(len(aliens))) 输出结果如下: 1234567{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'yellow', 'points': 10, 'speed': 'medium'}{'color': 'green', 'points': 5, 'speed': 'slow'}{'color': 'green', 'points': 5, 'speed': 'slow'}......Total number of aliens: 30 - 5.3.2 在字典中存储列表 有时候需要将列表储存在字典中,而不是将字典储存在列表中:例一: 12345678910#储存所点比萨的信息pizza = { 'crust' : 'thick' , 'toppings' : ['mushrooms' , 'extra chees'] , }#概述所点的比萨print(\"You ordered a \" + pizza['crust'] + \"-crust pizza\" + \"with the following toppings :\" )for topping in pizza['toppings']: print(\"\\t\" + topping) 输出结果如下: 123You ordered a thick-crust pizzawith the following toppings : mushrooms extra chees 例二: 12345678910favorite_languages = { 'jen' : ['python' , 'ruby'] , 'sarah' : ['c'] , 'edward' : ['go' , 'ruby'] , 'phil' : ['python' , 'java'] , }for name , languages in favorite_languages.items(): print(\"\\n\" + name.title() + \"'s favorite languages are:\") for language in languages: print(\"\\t\" + language.title()) 输出结果如下: 123456789101112131415Jen's favorite languages are: Python RubySarah's favorite languages are: CEdward's favorite languages are: Go RubyPhil's favorite languages are: Python Java - 5.3.3 在字典中存储字典 123456789101112131415161718users = { 'aeinstein' : { 'first' : 'albert' , 'last' : 'einstein' , 'location' : 'princeton' , } , 'mcurie' : { 'first' : 'marie' , 'last' : 'curie' , 'location' : 'paris' , } , }for username , user_info in users.items(): print(\"\\nUsername : \" + username) full_name = user_info['first'] + \" \" + user_info['last'] location = user_info['location'] print(\"\\tFull name : \" + full_name.title()) print(\"\\tlocation : \" + location .title()) 输出结果如下: 12345678Username : aeinstein Full name : Albert Einstein location : PrincetonUsername : mcurie Full name : Marie Curie location : Paris","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"字典","slug":"字典","permalink":"https://www.itrhx.com/tags/字典/"}]},{"title":"Python3 基础学习笔记 C04","slug":"A10-Python3-basic-C04","date":"2018-10-23T16:57:39.886Z","updated":"2019-09-24T12:45:32.554Z","comments":true,"path":"2018/10/24/A10-Python3-basic-C04/","link":"","permalink":"https://www.itrhx.com/2018/10/24/A10-Python3-basic-C04/","excerpt":"Python3 基础学习笔记第四章 —— 【if语句】","text":"Python3 基础学习笔记第四章 —— 【if语句】 - 4.1 一个简单的数列 给定一个汽车列表,将其中每一辆汽车的名称打印出来,要求打印 ‘bmw’ 时所有字母都要大写,其余名称只需要首字母大写: 123456cars = ['audi' , 'bmw' , 'subaru' , 'toyota']for car in cars: if car == 'bmw': print(car.upper())else: print(car.title()) 输出结果如下: 1234AudiBMWSubaruToyota - 4.1.1 检查特定值是否包含在列表当中 要判断特定的值是否已包含在列表当中,可使用关键字 in 1234user_names = ['andia' , 'david' , 'liwa']user = 'andia'if user in user_names: print(user.title() + \"is in user_name.\") 输出结果如下: 1Andiais in user_name. 要判断特定的值是否不包含在列表当中,可使用关键字 not in 1234user_names = ['andia' , 'david' , 'liwa']user = 'kivle'if user not in user_names: print(user.title() + \"is not in user_name.\") 输出结果如下: 1Kivleis not in user_name. - 4.2 if-else 语句 1234567age = input(\"请输入你的年龄查看是否可以去网吧:\")if int(age) >= 18: print(\"You are old enough to go to the net bar!\") print(\"You should go to net bar less,study more!\")else: print(\"You are too young to go to the net bar!\") print(\"Wait until you are 18 to go to the net bar!\") 分别输入19和15,输出结果如下: 123请输入你的年龄查看是否可以去网吧:19You are old enough to go to the net bar!You should go to net bar less,study more! 123请输入你的年龄查看是否可以去网吧:15You are too young to go to the net bar!Wait until you are 18 to go to the net bar! - 4.3 if-elif-else 结构 12345678age = 12if age < 4: price = 0elif age < 18: price = 5else: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果如下: 1Your admission cost is $5. - 4.3.1 使用多个 elif 代码块 12345678910age = 20if age < 4: price = 0elif age < 18: price = 5elif age < 65: price = 15else: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果如下: 1Your admission cost is $15. - 4.3.2 省略 else 代码块 Python并不要求 if-elif 结构后面必须有 else 代码块: 12345678910age = 20if age < 4: price = 0elif age < 18: price = 5elif age < 65: price = 15elif age >= 65: price = 10print(\"Your admission cost is $\" + str(price) + \".\") 输出结果仍与3.3.1一样 - 4.4 测试多个条件 if-elif-else结构功能强大,但仅适用于只有一个条件满足的情况:遇到通过了的测试后,Python就会跳过余下的测试: 12345678 names = ['Zhangshan' , 'Wanger']if 'Zhangshan' in names: print(\"Zhangshan is here!\")if 'Wanger' in names: print(\"Wanger is here!\")if 'Xiaoming' in names: print(\"Xiaoming is here!\")print(\"All the students are here!\") 输出结果如下: 123Zhangshan is here!Wanger is here!All the students are here! 相同的程序,如果使用 if-elif-else 结构,代码将不能正确运行: 12345678names = ['Zhangshan' , 'Wanger']if 'Zhangshan' in names: print(\"Zhangshan is here!\")elif 'Wanger' in names: print(\"Wanger is here!\")elif 'Xiaoming' in names: print(\"Xiaoming is here!\")print(\"All the students are here!\") 输出结果如下:12Zhangshan is here!All the students are here! 总之,如果我们只想执行一个代码块,就使用 if-elif-else 结构;如果要运行多个代码块,就必须使用一系列独立的 if 语句! - 4.5 使用 if 语句处理列表 - 4.5.1 检查特殊元素对3.4例子改版,加入姓名 ‘Xiaoming’,当检索到Xiaoming时告诉他,他妈妈叫他回家吃饭1234567names = ['Zhangshan' , 'Wanger' , 'Xiaoming']for name in names: if name == 'Xiaoming': print(\"Xiaoming,Your mother told you to go home for dinner!\") else: print(name +\"is here!\")print(\"All the students are here!\") 输出结果如下: 1234Zhangshanis here!Wangeris here!Xiaoming,Your mother told you to go home for dinner!All the students are here! - 4.5.2 确定列表不是空的 在检索姓名前检查姓名是否为空,不为空则打印出所有姓名,为空则提示没有姓名: 1234567names = []if names: for name in names: print(name +\" is here!\") print(\"All the students are here!\")else: print(\"There is no students!\") 输出结果如下: 1There is no students! 在if语句中将列表名用在条件表达式中时,Python将在列表至少包含一个元素时返回Ture,并在列表为空时返回False - 4.5.3 使用多个列表 两个列表names_1和names_2,要求输出既在names_2中又在names_1中的元素: 123456names_1 = ['Zhangshan' , 'Liyang' , 'Wanger' , 'Tangyang' , 'Xiaoming']names_2 = ['Liyang' , 'Zhangwei' , 'Tangyang']for names in names_2: if names in names_1: print(names +\" is here!\")print(\"All the students are here!\") 输出结果如下: 123Liyang is here!Tangyang is here!All the students are here!","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"if语句","slug":"if语句","permalink":"https://www.itrhx.com/tags/if语句/"}]},{"title":"Python3 基础学习笔记 C03","slug":"A09-Python3-basic-C03","date":"2018-10-11T15:03:18.487Z","updated":"2019-09-24T12:45:28.649Z","comments":true,"path":"2018/10/11/A09-Python3-basic-C03/","link":"","permalink":"https://www.itrhx.com/2018/10/11/A09-Python3-basic-C03/","excerpt":"Python3 基础学习笔记第三章 —— 【操作列表】","text":"Python3 基础学习笔记第三章 —— 【操作列表】 - 3.1遍历整个列表 使用 for 循环来遍历整个列表: 123names = ['alice' , 'david' , 'liwei']for name in names:print(name) 输出结果如下: 123alicedavidliwei for循环让Python从列表names中取出一个名字,并将其储存在变量name中,最后 让Python打印前面储存到变量name中的名字,对于列表中的每个名字,Python都将 重复执行后两行代码,将列表names中的每个名字都打印出来 - 3.1.1在for循环中执行更多的操作 在for循环中,可对每个元素执行任何操作,下面对前面的示例进行扩展: 例一:123names = ['alice' , 'david' , 'liwei']for name in names: print(name.title() + \", that was a good man!\") 输出结果如下: 123Alice, that was a good man!David, that was a good man!Liwei, that was a good man! 例二: 12345names = ['alice' , 'david' , 'liwei']for name in names: print(name.title() + \", that was a good man!\") print(\"I can't wait to see you again,\" + name.title() + \".\\n\")print(\"Nice to meet you!\") 输出结果如下: 12345678910Alice, that was a good man!I can't wait to see you again,Alice.David, that was a good man!I can't wait to see you again,David.Liwei, that was a good man!I can't wait to see you again,Liwei.Nice to meet you! - 3.2 range()函数 Python使用range()函数能够轻松地生成一系列的数字 Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表; Python3 list() 函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表; Python2 range() 函数返回的是列表 例一:12for i in range(1,5): print(i) 输出结果如下: 12341234 例二:12for i in range(5): print(i) 输出结果如下:1234501234 例三:123456789101112>>> list(range(5))[0, 1, 2, 3, 4]>>> list(range(0))[]>>>list(range(0, 30, 5))[0, 5, 10, 15, 20, 25]>>> list(range(0, 10, 2))[0, 2, 4, 6, 8]>>> list(range(0, -10, -1))[0, -1, -2, -3, -4, -5, -6, -7, -8, -9]>>> list(range(1, 0))[] 例四: 12345squares = []for value in range(1,11): square = value ** 2 squares.append(square)print(squares) 输出结果如下: 1[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] - 3.2.1 对数字列表执行简单的统计计算 1234567>>> digits = [1, 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0]>>> min(digits)0>>>max(digits)9>>>sum(digits)45 - 3.2.2 列表解析 列表解析能够让比如3.2中的例四更加简化,只需要一行代码就能生成这样的列表,列表解析将for循环和创建新元素的代码合并成一行,并自动附加新元素: 12squares = [value ** 2 for value in range(1,11)]print(squares) 在这个示例中,for循环为for value in range(1,11),它将值1~10提供给表达式value ** 2输出结果如下: 1[1, 4, 9, 16, 25, 36, 49, 64, 81, 100] - 3.3 使用列表的一部分 处理列表的部分元素——Python称之为切片 - 3.3.1 切片 1234567891011list = ['a','b','c','d','e','f']print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾print(list[4:1]) #从左到右索引,因此为空值print(list[-1:-3]) #从左到右索引,因此为空值print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2print(list[::-1]) #反向取值,反向输出列表 - 3.3.2 遍历列表 1234players = ['charles' , 'martina' , 'michael' , 'florence' , 'eli']print(\"Here are the first three players on my team:\")for player in players[:3]: print(player.title()) 输出结果如下: 1234Here are the first three players on my team:CharlesMartinaMichael - 3.3.3 复制列表 要复制列表,可以创建一个包含整个列表的切片,方法是同时省略起始索引和终止索引([:]),这让Python创建一个始于第一个元素,终止于最后一个元素的切片,即复制整个列表: 123456my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods[:]print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake'] 为核实我们的确有两个列表,下面在每个列表中都添加一种食品,并核实每个列表都记录了相应人员喜欢的食品:12345678910my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods[:]my_foods.append('cannoli')friend_foods.append('ice cream')print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake', 'ice cream'] 输出结果表明,’cannoli’包含在我喜欢的食品列表中,而’ice cream’没有;’ice cream’包含在我朋友喜欢的食品中,而’cannoli’没有,假如我们只是简单的将my_foods赋给friend_foods,就不能得到两个列表。下面是错误示例: 12345678910my_foods = ['pizza' , 'falafel' , 'carrot cake']friend_foods = my_foods #错误写法my_foods.append('cannoli')friend_foods.append('ice cream')print(\"My favorite foods are:\")print(my_foods)print(\"\\nMy friend's favorite foods are:\")print(friend_foods) 错误示例输出结果如下: 12345My favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']My friend's favorite foods are:['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream'] - 3.4 元组 Python将不能修改的值称为不可变的,而不可变的列表被称为元组 - 3.4.1 定义元组 元组看起来就像是列表,但元组使用圆括号而不是方括号来标识,定义元组后,就可以使用索引来访问其元素,就像访问列表元素一样: 123dimensions = (200,50)print(dimensions[0])print(dimensions[1]) 输出结果如下: 1220050 如果尝试修改元组中元素的值,将会导致Python返回类型错误消息,由于试图修改元组的操作是被禁止的,因此Python指出不能给元组的元素赋值: 12dimensions = (200,50)dimensions[0] = 300 将会报错: 1234Traceback (most recent call last): File \"dimensions.py\", line 2, in <module> dimensions[0] = 300TypeError: 'tuple' object does not support item assignment - 3.4.2 遍历元组中所有的值 像列表一样,元组也可以使用for循环来遍历元组中的所有值: 例一:123dimensions = (200,100,50,6)for dimension in dimensions: print(dimension) 输出结果如下: 1234200100506 例二: 123dimensions = (200,100,50,6)for dimension in dimensions[:3]: print(dimension) 输出结果如下: 12320010050 - 3.4.3 修改元组变量 虽然不能修改元组元素,但是可以给储存元组的变量赋值: 123456789dimensions = (200,50)print(\"Original dimensions:\")for dimension in dimensions: print(dimension) dimensions = (400,100)print(\"\\nModified dimensions:\")for dimension in dimensions: print(dimension) 输出结果如下: 1234567Original dimensions:20050Modified dimensions:400100 我们首先定义了一个元组,并将其储存的尺寸打印了出来;然后将一个新元组储存到变量dimensions中,打印新的尺寸;相比于列表,元组是更简单的数据结构。如果需要储存的一组值在程序的整个生命周期内都不变,可使用元组","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"操作列表","slug":"操作列表","permalink":"https://www.itrhx.com/tags/操作列表/"}]},{"title":"Python3 基础学习笔记 C02","slug":"A08-Python3-basic-C02","date":"2018-09-15T17:59:48.972Z","updated":"2019-09-24T12:45:24.985Z","comments":true,"path":"2018/09/16/A08-Python3-basic-C02/","link":"","permalink":"https://www.itrhx.com/2018/09/16/A08-Python3-basic-C02/","excerpt":"Python3 基础学习笔记第二章 —— 【列表】","text":"Python3 基础学习笔记第二章 —— 【列表】 - 2.1列表是什么 列表由一系列按特定顺序的元素组成,在 Python 中用方括号( [ ] )来表示列表,并用逗号来分隔其中的元素,例: 12345list1 = ['a','b','c','d','e','f']list2 = ['abc', 'xyz', 2018, 2020]list3 = [1, 2, 3, 4, 5 ,6]list4 = [\"a\", \"b\", \"c\", \"d\"]print(list1, list2, list3 ,list4) 输出结果如下: 1['a', 'b', 'c', 'd', 'e', 'f'] ['abc', 'xyz', 2018, 2020] [1, 2, 3, 4, 5, 6] ['a', 'b', 'c', 'd'] - 2.1.1访问列表元素 列表是有序集合,因此要访问列表的元素,只需要将该元素的位置或索引告诉Python即可,注意:在Python中的第一个列表元素的索引为0,而不是1 12345list = ['a','b','c','d','e','f']print(list[0])print(list[3])print(list[-1]) #Python为访问最后一个列表元素提供了一种特殊语法,通过将索引指定为-1,可以让Python返回最后一个列表元素print(list[-3]) 输出结果如下: 1234adfd - 2.1.2列表切片 1234567891011list = ['a','b','c','d','e','f']print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾print(list[4:1]) #从左到右索引,因此为空值print(list[-1:-3]) #从左到右索引,因此为空值print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2print(list[::-1]) #反向取值,反向输出列表 输出结果如下: 12345678910['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c']['d', 'e', 'f']['b', 'c', 'd'][][]['d', 'e']['b', 'd']['f', 'e', 'd', 'c']['f', 'e', 'd', 'c', 'b', 'a'] - 2.1.3使用列表中的各个值 可像使用其他变量一样使用列表中的各个值,例如,我们可以使用拼接根据列表中的值来创建消息: 123list = ['python', 'c', 'c++', 'java', 'php']message = \"My favorite language is \" + list[0].title() + \"!\"print(message) 输出结果如下: 1My favorite language is Python! - 2.1.4修改元素 修改列表元素的语法与访问列表元素的语法类似,要修改列表元素,可指定列表名和要修改的元素的索引,再次指定该元素的新值: 1234names = ['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']print(names)names[1] = 'lifang'print(names) 输出结果如下: 12['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']['zhangsan', 'lifang', 'wanger', 'liming', 'xiaowang'] - 2.1.5添加元素 - 使用方法 append() 在列表末尾添加元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.append('g')print(list)输出结果如下:12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'd', 'e', 'f', 'g'] - 使用方法 insert() 在列表指定位置添加元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.insert(2,\"h\") #其中括号里的数字表示要插入的位置,此后面的元素将右移一个位置print(list) 输出结果如下: 12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'h', 'c', 'd', 'e', 'f', 'g'] - 2.1.6删除元素 - 使用 del 语句删除元素 1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)del list[3]print(list) 输出结果如下: 12list = ['a', 'b', 'c', 'd', 'e', 'f']list = ['a', 'b', 'c', 'e', 'f'] - 使用方法 pop() 删除最后一个元素方法 pop() 可以删除列表末尾的元素,并让你能够接着使用它。术语弹出(pop)源自这样的类比:列表就像是一个栈,而删除列表末尾的元素就相当于弹出栈顶元素:12345list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)new_list = list.pop()print(list)print(new_list)输出结果如下:123['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'd', 'e']f - 使用方法 pop() 删除任意位置元素可以使用 pop() 来删除列表中任何位置的元素,只需要在括号中指定要删除的元素的索引即可:12345list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)new_list = list.pop(1)print(list)print(new_list)输出结果如下:123['a', 'b', 'c', 'd', 'e', 'f']['a', 'c', 'd', 'e', 'f']b - 使用方法 remove() 删除未知位置元素当我们不知道元素的位置,只知道元素的值的时候,就可以使用方法 remove()1234list = ['a', 'b', 'c', 'd', 'e', 'f']print(list)list.remove('d')print(list)输出结果如下:12['a', 'b', 'c', 'd', 'e', 'f']['a', 'b', 'c', 'e', 'f'] # - 2.1.7使用方法 index() 查找指定元素位置 12list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]print(list.index('c')) 输出结果如下: 12 - 2.1.8使用方法 count() 统计指定元素数量 12list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]print(list.count('a')) 输出结果如下: 12 - 2.1.9清空列表 123list = [\"a\", \"b\", \"c\", \"d\", \"e\", \"a\"]list.clear()print(list) 输出结果如下: 1[] - 2.2组织列表 在创建的列表中,元素的排列顺序常常是无法预测的,因为我们并非总能控制用户提供数据的顺序。有时候,我们希望保留列表元素最初的排列顺序,而有时候又需要调整排列顺序。Python提供了很多组织列表的方式,可根据具体情况选用 - 2.2.1使用方法 sort() 对列表进行永久排序 使用方法 sort() 可以对列表按照特殊符号,数字,大写字母,小写字母顺序进行永久排序: 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.sort()print(cars) 输出结果如下: 1['audi', 'bmw', 'subaru', 'toyota'] 还可以按与字母顺序相反的顺序排列列表元素,只需要向 sort() 方法传递参数 reverse = True 就可以了: 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.sort(reverse = True)print(cars) 输出结果如下: 1['toyota', 'subaru', 'bmw', 'audi'] - 2.2.2使用函数 sorted() 对列表进行临时排序 要保留列表元素原来的排列顺序,同时以特定的顺序呈现它们,可使用函数sorted()。函数sorted()让你能够按特定顺序显示列表元素,同时不影响它们在列表中的原始排列顺序: 123456789cars = ['bmw', 'audi', 'toyota', 'subaru']print(\"Here is the original list:\")print(cars)print(\"\\nHere is the sorted list:\")print(sorted(cars))print(\"\\nHere is the sorted reverse list:\")print(sorted(cars, reverse=True))print(\"\\nHere is the original list again:\")print(cars) 输出结果如下: 1234567891011Here is the original list:['bmw', 'audi', 'toyota', 'subaru']Here is the sorted list:['audi', 'bmw', 'subaru', 'toyota']Here is the sorted reverse list:['toyota', 'subaru', 'bmw', 'audi']Here is the original list again:['bmw', 'audi', 'toyota', 'subaru'] - 2.2.3使用方法 reverse() 对列表进行反向排序 要反转列表元素的排列顺序,可使用方法 reverse() 123cars = ['bmw', 'audi', 'toyota', 'subaru']cars.reverse()print(cars) 输出结果如下: 1['subaru', 'toyota', 'audi', 'bmw'] - 2.2.4确定列表的长度 使用函数 len() 可以快速获悉列表的长度: 123>>>cars = ['bmw', 'audi', 'toyota', 'subaru']>>>len(cars)4 - 2.2.5合并列表 - 使用方法 extend() 合并列表 12345list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1.extend(list2) #将列表list2添加到list1当中去print(list1)print(list2) 输出结果如下: 12[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd'] - 使用 “+” 号合并列表 1234list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']print(list1 + list2)print(list2 + list1) 输出结果如下: 12[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd', 1, 2, 3, 4] - 使用切片合并列表 1234567891011121314list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[len(list1) : len(list1)] = list2 #len(list1)代表要将list2插入list1中的位置print(list1)list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[0 :0] = list2print(list1)list1 = [1, 2, 3, 4]list2 = ['a', 'b', 'c', 'd']list1[1:1] = list2print(list1) 输出结果如下: 123[1, 2, 3, 4, 'a', 'b', 'c', 'd']['a', 'b', 'c', 'd', 1, 2, 3, 4][1, 'a', 'b', 'c', 'd', 2, 3, 4]","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"列表","slug":"列表","permalink":"https://www.itrhx.com/tags/列表/"}]},{"title":"Python3 基础学习笔记 C01","slug":"A07-Python3-basic-C01","date":"2018-09-13T12:47:09.608Z","updated":"2019-12-29T07:27:42.544Z","comments":true,"path":"2018/09/13/A07-Python3-basic-C01/","link":"","permalink":"https://www.itrhx.com/2018/09/13/A07-Python3-basic-C01/","excerpt":"Python3 基础学习笔记第一章 —— 【变量和简单数据类型】","text":"Python3 基础学习笔记第一章 —— 【变量和简单数据类型】 - 1.1变量的命名和使用 变量名只能包含字母、数字和下划线。变量名可以字母或者下划线打头,但不能以数字开头,例如,可以将变量命名为message_1,但不能将其命名为1_message 变量名不能包含空格,但可使用下划线来分割其中的单词,例如,变量名greeting_message可行,但变量名greeting message会引发错误 不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词,如print 变量名应既简短又具有描述性,例如,name比n好,student_name比s_n好,name_length比length_of_persons_name好 慎用小写字母l和大写字母O,因为它们可能被人看错成数字1和0 - 1.2字符串 字符串就是一系列字符,在Python中,用引号括起来的都是字符串,其中的引号可以是单引号也可以双引号: 12\"This is a string.\"'This is also a string.' 这种灵活性让我们能够在字符串中包含引号和撇号: 123'I told my friend,\"Python is my favorite language!\"'\"The language 'Python' is named er Monty Python,not the snake.\"\"One of Python's strengths is i diverse and supportive community.\" - 1.2.1使用方法修改字符串的大小写三种处理方法如下:123title() #将字符串每个单词的首字母都改为大写upper() #将字符串的每个字母都改为大写lower() #将字符串的每个字母都改为小写 例如:1234message = \"I love you!\"print(name.title())print(name.upper())print(name.lower()) 输出结果如下:123I Love You!I LOVE YOU!i love you! - 1.2.2合并(拼接)字符串Python使用加号(+)来合并字符串,举例说明: 12345first_name = \"I\"second_name = \"love\"third_name = \"python\"full_name = first_name + \" \" + second_name + \" \" + third_timeprint(full_name.title() + \"!\") 输出结果如下: 1I Love Python! - 1.2.3使用制表符或换行符来添加空白添加横向制表符: 12>>>print(\"\\tPython\") Python 添加换行符: 12345>>>print(\"C\\nC++\\nPython\\nJavaScript\")CC++PythonJavaScript 附表:Python转义符 - 1.2.4删除空白在Python中可用 lstrip()、rstrip()、strip() 分别删除字符串开头、结尾、全部的空白,举例说明: 123456789>>>message = ' python '>>>message' python '>>>message.lstrip()'python '>>>message.rstrip()' python'>>>message.strip()'python' 如果要永久删除字符串中的空白,必须将删除操作的结果存回到变量中: 1234>>>message = ' python '>>>message = message.strip()>>>message'python' - 1.3数字在编程中,经常使用数字来记录游戏得分、表示可视化数据、储存Web应用信息等。Python根据数字的用法以不同的方式处理它们 - 1.3.1整数在Python中,可对整数执行加(+)减(-)乘(*)除(/)乘方(**)运算,同时也支持运算次序: 12345678910111213141516>>>3 + 25>>>3 - 21>>>3 * 26>>>3 \\ 21.5>>>3 ** 29>>>3 ** 327>>>2 + 3 * 414>>>(2 + 3) * 420 - 1.3.2浮点数Python将带小数点的数字都称为浮点数: 1234>>>0.1 + 0.10.2>>>2 * 0.20.4 需要注意的是,结果包含的小数位可能是不确定的,就现在而言,暂时忽略多余的小数位即可: 1234>>>0.2 + 0.10.30000000000000004>>>3 * 0.10.30000000000000004 - 1.3.3使用函数 str() 避免错误错误例子: 123age = 23message = \"Happy \" + age + \"rd Birthday!\"print(message) 运行时会报错: 1234Traceback (most recent call last): File \"birthday.py\", line 2, in <module> message = \"Happy \" + age + \"rd Birthday!\"TypeError: must be str, not int 这是一个类型错误,意味着Python无法识别我们使用的信息。在这个例子中,Python发现我们使用了一个值为整数(int)的变量,但它不知道该如何解读这个值,这个变量表示的可能是数值23,也可能是字符2和3。像上面这样的字符串中使用整数时,需要显式地指出我们希望Python将这个整数用作字符串。为此,可调用函数 str(),它让Python将非字符串值表示为字符串: 123age = 23message = \"Happy \" + str(age) + \"rd Birthday!\"print(message) 输出结果如下: 1Happy 23rd Birthday! - 1.4注释注释让我们能够使用自然语言在程序中添加说明,Python中注释有三种方法: 123456789print(\"Hello Python!\")#这是单行注释'''这是多行注释这是多行注释'''\"\"\"这也是多行注释这也是多行注释\"\"\"","categories":[{"name":"Python3 学习笔记","slug":"Python3-学习笔记","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/"},{"name":"基础学习","slug":"Python3-学习笔记/基础学习","permalink":"https://www.itrhx.com/categories/Python3-学习笔记/基础学习/"}],"tags":[{"name":"数据类型","slug":"数据类型","permalink":"https://www.itrhx.com/tags/数据类型/"},{"name":"变量","slug":"变量","permalink":"https://www.itrhx.com/tags/变量/"}]},{"title":"VMware Pro 14 安装 Ubuntu 18.04 详细教程","slug":"A06-install-ubuntu18.04","date":"2018-09-09T13:14:29.532Z","updated":"2020-03-14T05:48:49.164Z","comments":true,"path":"2018/09/09/A06-install-ubuntu18.04/","link":"","permalink":"https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/","excerpt":"","text":"1.下载安装 VMware Workstation Pro 14 进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装 2.下载 Ubuntu 18.04 系统 进入 Ubuntu 官网,下载最新版 Ubuntu 系统镜像 3.在 VMware 中创建虚拟机打开安装好的 VMware Workstation Pro 14,选择创建新的虚拟机 在新建虚拟机向导中选择自定义(高级) 默认直接下一步,直到出现下图,再选择稍后安装操作系统 选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本 更改虚拟机名称及存放位置 为虚拟机指定处理器数量,默认即可 为虚拟机分配内存,太大了可能会导致卡顿,太小了也不好,推荐内存大小即可 以下均选择默认即可 选择创建新虚拟磁盘 选择将虚拟磁盘储存为单个文件 默认下一步 点击完成 此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Ubuntu 64 位,单击 Ubuntu 64 位,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Ubuntu 64 位镜像文件,点击“确定” 4.在虚拟机上安装 Ubuntu 系统单击 Ubuntu 64 位,选择“开启此虚拟机” 来到欢迎界面,选择好语言,点击“安装 Ubuntu” 选择键盘布局为“汉语” 更新和其他软件默认选择即可 安装类型选择“清除整个磁盘并安装 Ubuntu”,PS: 因为我们是新安装的系统,且在虚拟机中,所以可以选择清除整个磁盘,这个操作不会清除你原来电脑里面的东西 地区随便,在中国就行,默认即可 之后设置计算机名,密码 点击继续稍等一会就安装完成啦 安装过程中可能会出现的一些问题 1.在虚拟机上安装 Ubuntu 系统的过程中卡死不动 解决方法:关闭网络,重新安装即可 2.Ubuntu 不能全屏显示解决方法:方法①:安装 open-vm-tools: 1sudo apt-get install open-vm-tools 然后执行: 1sudo apt-get install open-vm* 重启即可全屏显示 方法②:在终端输入xrandr,并回车,我们就可以看到很多可以修改的分辨率,选择好分辨率后,比如我们要修改分辨率为 1920x1440 ,则在终端输入 xrandr -s 1920x1440,回车即可,注意 1920x1440 中间是小写字母 x,本人亲测此方法并不是很完美,不能完全适应屏幕 方法③:安装 VMware Tools:1、进入 Ubuntu 系统后,点击虚拟机上的【虚拟机】—>【安装 VMware Tools】,回到桌面即可看到一个 VMware Tools 的 图标2、复制 VMwareTools-10.0.10-4301679.tar.gz(版本根据自己的实际情况而定)到 home 目录下, 用命令 tar -xzvf VMwareTools-10.0.10-4301679.tar.gz 进行解压3、解压后 cd vmware_tools_distrib,打开终端4、输入“sudo ./vmware-install.pl”,输入用户密码后开始安装5、接下来会有很多地方需要你按 Enter或者 Yes6、当你看到出现 —the vmware team 的字样后就可以关闭窗口了,此时窗口就会自动全屏了,如果没有全屏,重启过后就可以了7、若还没有全屏显示,则将虚拟机的【查看】—>【自动调整大小】—>【自适应客户机】,都选上,即可实现全屏","categories":[{"name":"Linux","slug":"Linux","permalink":"https://www.itrhx.com/categories/Linux/"}],"tags":[{"name":"VMware","slug":"VMware","permalink":"https://www.itrhx.com/tags/VMware/"},{"name":"Ubuntu","slug":"Ubuntu","permalink":"https://www.itrhx.com/tags/Ubuntu/"}]},{"title":"主流 Markdown 编辑器推荐","slug":"A05-markdown-editor","date":"2018-08-29T15:02:46.857Z","updated":"2020-03-14T05:44:24.275Z","comments":true,"path":"2018/08/29/A05-markdown-editor/","link":"","permalink":"https://www.itrhx.com/2018/08/29/A05-markdown-editor/","excerpt":"","text":"Markdown ,2004年由 John Gruberis 设计和开发,是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式,以下将介绍目前比较流行的一些 Markdown 编辑器(排名不分先后) - MarkdownPad 目前分为 MarkdownPad2 和 MarkdownPad Pro 版本,后者收费,我们使用前者足矣,用户可以通过键盘快捷键和工具栏按钮来使用或者移除 Markdown 各种语法格式,支持自定义配色方案、字体、大小和布局 、即时HTML预览、HTML和PDF导出,被很多人称赞为 Windows 平台最好用的 Markdown 编辑器,实用性强,仅支持 Windows 系统,个人觉得在 Windows 10 系统上界面并不是很好看,有时候添加音乐什么的,资源多了,实时预览会显示资源加载失败,点击此处访问 MarkdownPad 官网 - BookPad 无意间在 Microsoft Store 上发现的,完美搭配 Win10 系统,界面非常简洁漂亮,2017年9月份发布,大小30.82 MB,官方网站:https://sosfos.wordpress.com/ ,收费13人民币,可免费使用7天,各种功能应有尽有,和其他编辑器不相上下,本来想着百度百度看看有没有破解版,结果全网看不见 BookPad 的影子,估计是新出来的还不为人所知吧,可以直接在 Microsoft Store 搜索下载,或者点击链接获取:https://www.microsoft.com/store/apps/9N6P5ZH2SJSX - 小书匠 分为免费版和收费版,收费版¥20/年,其实免费版的功能已经足够强大了,多种编辑模式、多种主题选择、多种编辑器实现、丰富的语法支持、第三方同步、强大的文件管理功能,让人使用一次就爱上了它,支持 Windows 和 Web,推荐使用,点击此处访问小书匠官网 - TyporaTypora 同样支持 Windows、OS X 和 Linux,Typora 支持即时渲染技术,这也是与其他 Markdown 编辑器最显著的区别,支持数学编辑,可与 Word 直接格式转换,在 Pandoc 的支持下进行多种文档格式转换,Typora 适合那些对码字手速和排版顺畅度有要求的人群,譬如码农、网站小编等,点击此处访问 Typora 官网 - Visual Studio CodeVisual Studio Code 是众所周知的神器,是微软推出一款轻量级的文本编辑工具,类似于 Sublime,它已经默认集成 Markdown 文档编辑插件,原生就支持高亮 Markdown 的语法,但想要实时预览还需要选择 Markdown: Open Preview to the Side 命令实现,相关教程请点击此处,点击此处 访问 Visual Studio Code 官网 - MarxicoMarxico 中文名马克飞象,提供桌面客户端以及离线 Chrome App,支持移动端 Web,可以直接把文本存到印象笔记,点击此处访问 Marxico,点击此处访问 马克飞象 - Sublime Text 3Sublime Text 3 是基于 Vim 开发的跨平台代码编辑器,收费80美元,好像可以免费试用,支持 OS X、Windows、Ubuntu 等 UNIX 及 Linux 操作系统,由于其功能的多样性而广受好评,界面简约大方,定位专业,原生支持的编程语言就多达十几种,通过第三方插件,还能实现更多语法的支持,其中就包括 Markdown ,但也有个缺点,就是不能实时预览,但是用户可以通过 Markdown Preview 的插件实现对 Markdown 的预览,具体教程请点击此处查看,点击此处访问 Sublime Text 官网 - Mou Mou 是一款由国人独立开发者罗晨开发的实时预览型 Markdown 编辑器,仅支持 OS X操作系统,是目前同类应用中对汉字兼容性最好的作品,也是目前最好用的免费 Markdown 编辑器,提供语法高亮、在线预览、同步滚动、全屏模式,支持自定保存、自动匹配,允许自定义主题,支持 CSS,HTML 和 PDF 导出等功能,点击此处访问 Mou 官网 - AtomAtom 是 Github 专门为程序员推出的一个跨平台文本编辑器,具有简洁和直观的图形用户界面,并有很多有趣的特点:支持CSS,HTML,JavaScript等网页编程语言,当然也支持 Markdown ,支持宏,自动完成分屏功能,集成了文件管理器,点击此处访问 Atom 官网 - Smark国人编写的开源软件,Windows / Linux 等主流系统跨平台支持,完美支持 LaTex 数学公式、脚注、尾注等,支持使用本地 MathJax 调用,不需要在线访问 MathJax CDN,用户可配置的 Markdown 语法高亮显示,美观整洁,多种格式文件导出支持,简洁友好的界面布局,完备的各类快捷键,能极大地提高工作效率,点击此处访问 Smark 官网 - HaroopadHaroopad 覆盖三大主流桌面系统,支持 Windows、OS X 和 Linux,多种主题样式供你选择,语法标亮支持 54 种编程语言,该工具重点推荐 Ubuntu/Linux 用户使用,点击此处访问 Haroopad 官网 - CuteMarkEdCuteMarkEd 是一个基于qt5的跨平台的 Markdown 编辑器,开源的, 提供实时 HTML 预览、数学表达式、源码高亮和PDF导出,点击此处 访问 CuteMarkEd 官网 - MarkPadMarkPad 是款开源的 Markdown 编辑器,与 Window 8 风格和谐友好的界面,可以直接在你的博客或者 GitHub 中打开、保存文档,直接将图片粘贴到 Markdown 文档中,点击此处访问 MarkPad 官网 - Cmd Markdown作业部落出品,是一款不错的工具和博客平台兼顾的产品,同时支持 Linux、Mac 和 Windows 操作系统,此外还提供 Web 在线创作,社交化批注、智能云同步,最简单的方法,满足多种写作需要,点击此处访问 Cmd Markdown 官网 - FarBox同样是一款不错的 Markdown 编辑器和博客平台兼顾的产品,让用户通过Dropbox(现在默认是自己的同步服务器)直接建立个人网站。FarBox编辑器免费,同时支持 Linux、Mac 和 Windows 操作系统,Farbox服务可以免费试用,在本地编辑器内写作自动同步发布在个人博客,对于希望有个人博客但却不愿折腾的小白来说,是个不错的选择,点击此处访问 FarBox 官网 - MiuMiu 是一款 Windows 下的 Markdown 编辑器,支持 Markdown 高亮、代码高亮、即时预览,以及可以快速发布到 Github Gist,小众软件,界面美观,已经找不到官网了,小众软件网有提供百度云下载,Miu 下载地址 - MacDownMacDown 引用了许多 Mou 的设计方式,仅支持 Mac ,开源免费,点击此处访问 MacDown 官网 - Ulysses一款由国外开发商 The Soulmen 制作的 Markdown 编辑器。与其它同类应用相比,Ulysses 最大的不同在于,它能根据内置的文件管理器,以及与 iCloud 云服务器的实时同步方案,达到最快捷的文章整理效率,支持OS X , iPad,26人民币每月,14天免费试用,点击此处访问 Ulysses 官网 - Byword一款轻量级的 Markdown 编辑器,支持Mac,iPhone和iPad,界面极简,功能强大,貌似要付费使用,点击此处 访问 Byword 官网 - MaHua一个在线编辑 Markdown 文档的编辑器,小众软件,VIM 快捷键支持,完美兼容 Github 的 Markdown 语法,界面稍许简陋,点击此处访问 MaHua - Dillinger来自国外的 Markdown 编辑器,漂亮强大,支持md、 html、pdf 文件导出,支持Dropbox、Github、Google Drive、Onedrive 一键保存,点击此处访问 Dillinger - CSDN中国专业IT社区CSDN (Chinese Software Developer Network) 创立于1999年,致力于为中国软件开发者提供知识传播、在线学习、职业发展等全生命周期服务。CSDN的在线编辑器功能强大,支持导出为HTML和md文件,注册账号后即可开始创作,点击此处访问CSDN官网 - 简书简书是一个优质的创作社区,你可以在线创作并发表到社区,是国内优质原创内容输出平台,简书从一开始就已经支持 Markdown 和富文本编辑,是一个为专门为作者打造的平台,点击此处访问简书官网 要细数 Markdown 编辑器的话,可能永远也数不尽,而且每个人的看法也不同,正所谓萝卜白菜各有所爱,什么编辑器不是最重要的,重要的是我们能写出优质的文章,不断学习进步!不断提升自我! 参考资料:《好用的Markdown编辑器一览》(By:月光)《10款流行的Markdown编辑器,总有一款适合你》(By:xiaoxiao_engineer)《解决作者们的焦虑:7 款优秀 Markdown 编辑工具推荐》(By:JailJT)","categories":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/categories/Markdown/"}],"tags":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/tags/Markdown/"},{"name":"编辑器","slug":"编辑器","permalink":"https://www.itrhx.com/tags/编辑器/"}]},{"title":"Hexo 博客主题个性化","slug":"A04-Hexo-blog-topic-personalization","date":"2018-08-27T13:25:24.452Z","updated":"2020-03-14T05:37:41.803Z","comments":true,"path":"2018/08/27/A04-Hexo-blog-topic-personalization/","link":"","permalink":"https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/","excerpt":"","text":"本文将讲述一些博客主题的美化、实用功能的添加,本文以作者 luuman 的 spfk 主题和作者 xaoxuu 的 Material X 主题为例,文章会不定时进行更新。文章涉及有关参考资料、教程、链接如有侵权请联系我删除! 本文在CSDN的链接:《Hexo 博客优化之博客美化》、《Hexo 博客优化之实用功能添加》,Hexo 博客专栏,从前期搭建到后期美化,帮您解决常见问题:《Github/Coding Pages + Hexo》,对您有帮助就点个赞吧❤️ 请注意:不同主题可能方法有些不同,相同主题不同版本,配置方法也有所差异! 博客美化前提条件:有一定的前端基础,了解 HTML、CSS、JS,了解 CSS 预处理语言 Sass、Less、Stylus,搞懂 hexo 的目录结构。 博客美化通用步骤:选定主题,认真阅读主题文档,分析主题目录结构,了解每个文件是对应网页哪个部分的,认真阅读美化教程,美化教程本质上只为你提供核心代码和思路,具体代码要添加到哪个地方,需要你自己搞懂主题结构,添加到需要的、合适的位置! 博客美化终极奥秘:创作第一,体验第二,避免繁杂,简洁为上! 【01】添加评论系统 主流的评论系统有很多,比如:网易云跟帖、多说、友言、畅言、来必力(LiveRe)、Disqus、Valine、Gitment等等,目前网易云跟帖、多说、友言都已经关闭了,还有些可能需要翻墙,比较麻烦,百度了一下,最后还是选择了来必力评论系统 进入来必力官网,注册一个账号(注册时可能需要翻墙) 注册完毕之后,登录,进入安装页面,选择 City 免费版安装,安装之后你会得到一段代码 我们打开主题文件下的 _config.yml 文件,添加如下代码: 在 \\themes\\hexo-theme-spfk\\layout\\_partial\\comments 文件夹下新建一个 livere.ejs 的文件,在里面填写来必力提供的代码: 123456789101112131415161718<!-- 来必力City版安装代码 --><div id=\"lv-container\" data-id=\"city\" data-uid=\"这里是你的uid\"> <script type=\"text/javascript\"> (function(d, s) { var j, e = d.getElementsByTagName(s)[0]; if (typeof LivereTower === 'function') { return; } j = d.createElement(s); j.src = 'https://cdn-city.livere.com/js/embed.dist.js'; j.async = true; e.parentNode.insertBefore(j, e); })(document, 'script'); </script> <noscript>为正常使用来必力评论功能请激活JavaScript</noscript></div><!-- City版安装代码已完成 --> 打开 \\themes\\hexo-theme-spfk\\layout\\_partial\\article.ejs 文件,在适当位置添加如下红框中的代码: 完成以上操作之后,我们就可以使用来必力评论系统了 【02】添加卡通人物 我在逛别人博客的时候偶然发现右下角居然有一个萌萌的卡通人物,还能根据你鼠标位置摇头,瞬间被吸引到了,赶紧也给自己博客添加一个吧!点击此处进入该项目地址 输入如下命令获取 live2d : 1$ npm install --save hexo-helper-live2d 输入以下命令,下载相应的模型,将 packagename 更换成模型名称即可,更多模型选择请点击此处,各个模型的预览请访问原作者的博客 1$ npm install packagename 打开站点目录下的 _config.yml 文件,添加如下代码: 1234567891011live2d: enable: true scriptFrom: local model: use: live2d-widget-model-haruto #模型选择 display: position: right #模型位置 width: 150 #模型宽度 height: 300 #模型高度 mobile: show: false #是否在手机端显示 设置好过后我们就拥有了一个卡通人物 【03】自定义鼠标指针样式 在 \\themes\\material-x\\source\\less\\_base.less 文件 body 样式里写入如下代码: 123456body { cursor: url(https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur),auto; background-color: @theme_background; ...... ......} 鼠标指针可以用 Axialis CursorWorkshop 这个软件自己制作,不同主题具体放的文件有所不同,确保在博客主体 body 的 CSS 文件中即可,其中的鼠标指针链接可替换成自己的,首先尝试加载 https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur ,如果该文件不存在或由于其他原因无效,那么 auto 会被使用,也就是自动默认效果,图片格式为.ico、.ani、.cur,建议使用.cur,如果使用.ani或者其他格式无效,原因是浏览器兼容问题,请阅读参考文档或者参考以下兼容表: 浏览器 最低版本 格式 Internet Explorer 6.0 .cur / .ani Firefox (Gecko), Windows and Linux 1.5 (1.8) .cur / .png / .gif / .jpg Firefox (Gecko) 4.0 (2.0) .cur / .png / .gif / .jpg / .svg Opera — — Safari (Webkit) 3.0 (522-523) .cur / .png / .gif / .jpg 拓展阅读:《CSS 鼠标样式 cursor属性》 (By:歪脖先生的博客) 【04】添加鼠标点击爱心效果 在 \\themes\\hexo-theme-spfk\\source\\js 下新建文件 love.js,在 love.js 文件中添加以下代码: 1!function(e,t,a){function n(){c(\".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 500%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}\"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText=\"left:\"+d[e].x+\"px;top:\"+d[e].y+\"px;opacity:\"+d[e].alpha+\";transform:scale(\"+d[e].scale+\",\"+d[e].scale+\") rotate(45deg);background:\"+d[e].color+\";z-index:99999\");requestAnimationFrame(r)}function o(){var t=\"function\"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement(\"div\");a.className=\"heart\",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement(\"style\");a.type=\"text/css\";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName(\"head\")[0].appendChild(a)}function s(){return\"rgb(\"+~~(255*Math.random())+\",\"+~~(255*Math.random())+\",\"+~~(255*Math.random())+\")\"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document); 在 \\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件末尾添加以下代码: 12<!-- 页面点击小红心 --><script type=\"text/javascript\" src=\"/js/love.js\"></script> 完成以上操作后,当我们点击鼠标的时候就可以看见爱心的特效了 【05】添加鼠标点击显示字体效果 在 \\themes\\hexo-theme-spfk\\source\\js 下新建文件 click_show_text.js,在 click_show_text.js 文件中添加以下代码: 123456789101112131415161718192021222324252627282930313233var a_idx = 0;jQuery(document).ready(function($) { $(\"body\").click(function(e) { var a = new Array (\"富强\", \"民主\", \"文明\", \"和谐\", \"自由\", \"平等\", \"公正\", \"法治\", \"爱国\", \"敬业\", \"诚信\", \"友善\"); var $i = $(\"<span/>\").text(a[a_idx]); a_idx = (a_idx + 1) % a.length; var x = e.pageX, y = e.pageY; $i.css({ \"z-index\": 5, \"top\": y - 20, \"left\": x, \"position\": \"absolute\", \"font-weight\": \"bold\", \"color\": \"#FF0000\" }); $(\"body\").append($i); $i.animate({ \"top\": y - 180, \"opacity\": 0 }, 3000, function() { $i.remove(); }); }); setTimeout('delay()', 2000);});function delay() { $(\".buryit\").removeAttr(\"onclick\");} 其中的社会主义核心价值观可以根据你自己的创意替换为其他文字 如果想要每次点击显示的文字为不同颜色,可以将其中 color 值进行如下更改: 1\"color\": \"rgb(\" + ~~(255 * Math.random()) + \",\" + ~~(255 * Math.random()) + \",\" + ~~(255 * Math.random()) + \")\" 然后在 \\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件末尾添加以下代码: 12<!--单击显示文字--><script type=\"text/javascript\" src=\"/js/click_show_text.js\"></script> 最终实现效果如下: 【06】添加鼠标点击烟花爆炸效果 在 \\themes\\material-x\\source\\js 目录下新建一个 fireworks.js 的文件,里面写入以下代码: 1\"use strict\";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color=\"#F00\",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:\"linear\",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(\".fireworks\");if(canvasEl){var ctx=canvasEl.getContext(\"2d\"),numberOfParticules=30,pointerX=0,pointerY=0,tap=\"mousedown\",colors=[\"#FF1461\",\"#18FF92\",\"#5A87FF\",\"#FBF38C\"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+\"px\",canvasEl.style.height=window.innerHeight+\"px\",canvasEl.getContext(\"2d\").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){\"sidebar\"!==e.target.id&&\"toggle-sidebar\"!==e.target.id&&\"A\"!==e.target.nodeName&&\"IMG\"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener(\"resize\",setCanvasSize,!1)}\"use strict\";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color=\"#F00\",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:\"linear\",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:\"easeOutExpo\",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(\".fireworks\");if(canvasEl){var ctx=canvasEl.getContext(\"2d\"),numberOfParticules=30,pointerX=0,pointerY=0,tap=\"mousedown\",colors=[\"#FF1461\",\"#18FF92\",\"#5A87FF\",\"#FBF38C\"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+\"px\",canvasEl.style.height=window.innerHeight+\"px\",canvasEl.getContext(\"2d\").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){\"sidebar\"!==e.target.id&&\"toggle-sidebar\"!==e.target.id&&\"A\"!==e.target.nodeName&&\"IMG\"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener(\"resize\",setCanvasSize,!1)}; 然后在 \\themes\\material-x\\layout\\layout.ejs 文件中写入以下代码: 123<canvas class=\"fireworks\" style=\"position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;\" ></canvas> <script type=\"text/javascript\" src=\"//cdn.bootcss.com/animejs/2.2.0/anime.min.js\"></script> <script type=\"text/javascript\" src=\"/js/fireworks.js\"></script> 最终效果: 【07】添加彩色滚动变换字体 在你想要添加彩色滚动变换字体的地方写入以下代码即可,其中文字可自行更改: 123456789101112131415161718192021222324252627282930313233343536373839404142<div id=\"binft\"></div> <script> var binft = function (r) { function t() { return b[Math.floor(Math.random() * b.length)] } function e() { return String.fromCharCode(94 * Math.random() + 33) } function n(r) { for (var n = document.createDocumentFragment(), i = 0; r > i; i++) { var l = document.createElement(\"span\"); l.textContent = e(), l.style.color = t(), n.appendChild(l) } return n } function i() { var t = o[c.skillI]; c.step ? c.step-- : (c.step = g, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : \"forward\" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = \"backward\", c.delay = a) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % o.length, c.direction = \"forward\")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(s, s + c.prefixP) : Math.min(s, t.length - c.skillP))), setTimeout(i, d) } var l = \"\", o = [\"青青陵上柏,磊磊涧中石。\", \"人生天地间,忽如远行客。\",\"斗酒相娱乐,聊厚不为薄。\", \"驱车策驽马,游戏宛与洛。\",\"洛中何郁郁,冠带自相索。\",\"长衢罗夹巷,王侯多第宅。\",\"两宫遥相望,双阙百余尺。\",\"极宴娱心意,戚戚何所迫?\"].map(function (r) { return r + \"\" }), a = 2, g = 1, s = 5, d = 75, b = [\"rgb(110,64,170)\", \"rgb(150,61,179)\", \"rgb(191,60,175)\", \"rgb(228,65,157)\", \"rgb(254,75,131)\", \"rgb(255,94,99)\", \"rgb(255,120,71)\", \"rgb(251,150,51)\", \"rgb(226,183,47)\", \"rgb(198,214,60)\", \"rgb(175,240,91)\", \"rgb(127,246,88)\", \"rgb(82,246,103)\", \"rgb(48,239,130)\", \"rgb(29,223,163)\", \"rgb(26,199,194)\", \"rgb(35,171,216)\", \"rgb(54,140,225)\", \"rgb(76,110,219)\", \"rgb(96,84,200)\"], c = { text: \"\", prefixP: -s, skillI: 0, skillP: 0, direction: \"forward\", delay: a, step: g }; i() }; binft(document.getElementById('binft')); </script> 最终效果: 【08】添加字数统计和阅读时长 先在博客目录下执行以下命令安装 hexo-wordcount 插件: 1$ npm i --save hexo-wordcount 注意:在 Material X 主题中,字数统计和阅读时长的功能我已提交 PR,在最新版本中,只需要安装插件后,在主题 config.yml 配置文件里,将 word_count 关键字设置为 true 即可,对于旧版本,可以通过以下方法实现: 以 Material X 主题(版本 1.2.1)为例,在 \\themes\\material-x\\layout\\_meta 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码: 123456789101112131415161718192021<% if(isPostList || !isPostList){ %> <% if (theme.word_count && !post.no_word_count) { %> <div style=\"margin-right: 10px;\"> <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-keyboard\"></i> <span class=\"post-meta-item-text\"> 字数统计: </span> <span class=\"post-count\"><%= wordcount(post.content) %>字</span> </span> </span> &nbsp; | &nbsp; <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-hourglass-half\"></i> <span class=\"post-meta-item-text\"> 阅读时长≈</span> <span class=\"post-count\"><%= min2read(post.content) %>分</span> </span> </span> </div> <% } %><% } %> 然后在主题的配置文件 _config.yml 找到 meta 关键字,将 word 填入 header 中: 123meta: header: [title, author, date, categories, tags, counter, word, top] footer: [updated, share] 最后在主题目录下的 _config.yml 添加以下配置即可 1word_count: true 效果图: 同样的,以 spfk 主题为例,在 \\themes\\hexo-theme-spfk\\layout\\_partial\\post 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码: 1234567891011121314151617<div style=\"margin-top:10px;\"> <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-keyboard-o\"></i> <span class=\"post-meta-item-text\"> 字数统计: </span> <span class=\"post-count\"><%= wordcount(post.content) %>字</span> </span> </span> &nbsp; | &nbsp; <span class=\"post-time\"> <span class=\"post-meta-item-icon\"> <i class=\"fa fa-hourglass-half\"></i> <span class=\"post-meta-item-text\"> 阅读时长: </span> <span class=\"post-count\"><%= min2read(post.content) %>分</span> </span> </span></div> 然后在 \\themes\\hexo-theme-spfk\\layout\\_partial\\article.ejs 中适当位置添加以下代码: 最后在主题目录下的 _config.yml 添加以下配置 1word_count: true 如果显示的位置不好,可以自行更改其位置,成功配置后的效果如下: 另外:要在博客底部显示所有文章的总字数,可以点击此处,根据你博客底部文件的类型选择相应的代码放在适当的位置即可,前提是要安装好 hexo-wordcount 插件,例如我使用 Material X 主题,在 \\themes\\material-x\\layout\\_partial 目录下的 footer.ejs 文件中添加如下代码: 12<i class=\"fas fa-chart-area\"></i><span class=\"post-count\">字数统计:<%= totalcount(site) %></span> 实现效果如下: 【09】添加背景音乐 打开网页版网易云音乐,选择你准备添加的背景音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,复制底下的HTML代码 然后将此代码放到你想要放的地方,比如放在博客的左侧,则打开 \\themes\\hexo-theme-spfk\\layout\\_partial\\left-col.ejs 文件,将复制的HTML代码粘贴进去,再进行适当的位置设置让播放器更美观,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐 最后效果如下: 这种网易云音乐外链的方式有很多局限性,因此推荐使用aplayer,GitHub地址为:https://github.com/MoePlayer/APlayer ,参考教程:《hexo上的aplayer应用》 【10】添加网站运行时间 一个比较好的小功能,可以看见自己的博客运行多久了,时间一天天的增加,成就感也会一天天增加的在 \\themes\\hexo-theme-spfk\\layout\\_partial\\footer.ejs 文件下添加以下代码: 1234567891011121314151617<span id=\"timeDate\">载入天数...</span><span id=\"times\">载入时分秒...</span><script> var now = new Date(); function createtime() { var grt= new Date(\"08/10/2018 17:38:00\");//在此处修改你的建站时间,格式:月/日/年 时:分:秒 now.setTime(now.getTime()+250); days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days); hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours); if(String(hnum).length ==1 ){hnum = \"0\" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum); mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = \"0\" + mnum;} seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum); snum = Math.round(seconds); if(String(snum).length ==1 ){snum = \"0\" + snum;} document.getElementById(\"timeDate\").innerHTML = \"本站已安全运行 \"+dnum+\" 天 \"; document.getElementById(\"times\").innerHTML = hnum + \" 小时 \" + mnum + \" 分 \" + snum + \" 秒\"; } setInterval(\"createtime()\",250);</script> 最后效果如下: 【11】添加百度统计 百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中 访问百度统计首页,注册一个账号后登陆,添加你的博客网站 接着点击代码获取,复制该代码 然后到目录 \\Hexo\\themes\\hexo-theme-spfk\\layout\\_partial 下新建一个 baidu-analytics.ejs 文件,里面粘贴你刚刚复制的代码 修改主题文件夹下的 _config.yml 文件,将你的key(图中涂掉部分)填写进去: 所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据 另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统 【12】浏览器网页标题恶搞当用户访问你的博客时点击到了其他网页,我们可以恶搞一下网页标题,呼唤用户回来,首先在目录 \\themes\\material-x\\source\\js 下新建一个 FunnyTitle.js 文件,在里面填写如下代码: 1234567891011121314151617// 浏览器搞笑标题var OriginTitle = document.title;var titleTime;document.addEventListener('visibilitychange', function () { if (document.hidden) { $('[rel=\"icon\"]').attr('href', \"/funny.ico\"); document.title = '╭(°A°`)╮ 页面崩溃啦 ~'; clearTimeout(titleTime); } else { $('[rel=\"icon\"]').attr('href', \"/favicon.ico\"); document.title = '(ฅ>ω<*ฅ) 噫又好啦 ~' + OriginTitle; titleTime = setTimeout(function () { document.title = OriginTitle; }, 2000); }}); 其中 funny.ico 是用户切换到其他标签后你网站的图标,favicon.ico 是正常图标,然后在 \\themes\\material-x\\layout\\layout.ejs 文件中添加如下代码: 12<!--浏览器搞笑标题--><script type=\"text/javascript\" src=\"/js/FunnyTitle.js\"></script> 再次部署博客后就可以看见标题搞笑的效果了: 【13】背景添加动态线条效果 在 \\Hexo\\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件中添加如下代码: 1234<!--动态线条背景--><script type=\"text/javascript\"color=\"220,220,220\" opacity='0.7' zIndex=\"-2\" count=\"200\" src=\"//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js\"></script> 其中: color:表示线条颜色,三个数字分别为(R,G,B),默认:(0,0,0) opacity:表示线条透明度(0~1),默认:0.5 count:表示线条的总数量,默认:150 zIndex:表示背景的z-index属性,css属性用于控制所在层的位置,默认:-1 最终实现效果: 【14】添加人体时钟 无意中发现了个有趣的人体时钟 HONE HONE CLOCK,作者是个日本人,点击此处访问作者博客,点击此处在作者原博客上查看动态样式,点击此处查看动态大图,如果你的博客上有合适的地方,加上一个人体时钟会很有趣的 实现代码: 12345<!--人体时钟背景透明--><script charset=\"Shift_JIS\" src=\"http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_tr.js\"></script><!--人体时钟背景白--><script charset=\"Shift_JIS\" src=\"http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_wh.js\"></script> 其他网页小挂件推荐: http://abowman.com/ 里面有很多有趣的小挂件,可以养养鱼、龟、狗、仓鼠等各式各样的虚拟宠物,能根据你的鼠标指针位置移动,直接复制代码就可以用 http://www.revolvermaps.com/ 它提供网站访客地理信息,可以以2D、3D等形式显示 http://www.amazingcounters.com/ 免费网站计数器,有非常多的样式供你选择,可以设置计数器初始数值,可以设置按访问量计数,也可以按独立访问者计数 https://www.seniverse.com/widget/get 心知天气提供基于Web的免费天气插件,可以为你的网站添加一项简洁美观的天气预报功能,并自动适配PC和手机上的浏览 【15】添加RSS订阅 RSS订阅是站点用来和其他站点之间共享内容的一种简易方式,即Really Simple Syndication(简易信息聚合),如果不会使用,可以参见百度百科:https://baike.baidu.com/item/RSS%E8%AE%A2%E9%98%85/663114 ;首先我们安装feed插件,在本地hexo目录下右键git bash here,输入以下命令: 1$ npm install hexo-generator-feed 等待安装完成后,打开hexo目录下配置文件的_config.yml,在末尾添加以下配置: 12345678910# Extensions## Plugins: http://hexo.io/plugins/#RSS订阅plugin:- hexo-generator-feed#Feed Atomfeed:type: atompath: atom.xmllimit: 20 随后打开主题配置文件_config.yml,添加以下配置: 1rss: /atom.xml 至此,RSS订阅功能添加完成 【16】添加网站雪花飘落效果 样式一和样式二分别如下: 实现方法:在 \\Hexo\\themes\\hexo-theme-spfk\\source\\js 目录下新建一个 snow.js 文件,粘贴以下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142/*样式一*/(function($){ $.fn.snow = function(options){ var $flake = $('<div id=\"snowbox\" />').css({'position': 'absolute','z-index':'9999', 'top': '-50px'}).html('&#10052;'), documentHeight = $(document).height(), documentWidth = $(document).width(), defaults = { minSize : 10, maxSize : 20, newOn : 1000, flakeColor : \"#AFDAEF\" /* 此处可以定义雪花颜色,若要白色可以改为#FFFFFF */ }, options = $.extend({}, defaults, options); var interval= setInterval( function(){ var startPositionLeft = Math.random() * documentWidth - 100, startOpacity = 0.5 + Math.random(), sizeFlake = options.minSize + Math.random() * options.maxSize, endPositionTop = documentHeight - 200, endPositionLeft = startPositionLeft - 500 + Math.random() * 500, durationFall = documentHeight * 10 + Math.random() * 5000; $flake.clone().appendTo('body').css({ left: startPositionLeft, opacity: startOpacity, 'font-size': sizeFlake, color: options.flakeColor }).animate({ top: endPositionTop, left: endPositionLeft, opacity: 0.2 },durationFall,'linear',function(){ $(this).remove() }); }, options.newOn); };})(jQuery);$(function(){ $.fn.snow({ minSize: 5, /* 定义雪花最小尺寸 */ maxSize: 50,/* 定义雪花最大尺寸 */ newOn: 300 /* 定义密集程度,数字越小越密集 */ });}); 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128/*样式二*//* 控制下雪 */function snowFall(snow) { /* 可配置属性 */ snow = snow || {}; this.maxFlake = snow.maxFlake || 200; /* 最多片数 */ this.flakeSize = snow.flakeSize || 10; /* 雪花形状 */ this.fallSpeed = snow.fallSpeed || 1; /* 坠落速度 */}/* 兼容写法 */requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || function(callback) { setTimeout(callback, 1000 / 60); };cancelAnimationFrame = window.cancelAnimationFrame || window.mozCancelAnimationFrame || window.webkitCancelAnimationFrame || window.msCancelAnimationFrame || window.oCancelAnimationFrame;/* 开始下雪 */snowFall.prototype.start = function(){ /* 创建画布 */ snowCanvas.apply(this); /* 创建雪花形状 */ createFlakes.apply(this); /* 画雪 */ drawSnow.apply(this)}/* 创建画布 */function snowCanvas() { /* 添加Dom结点 */ var snowcanvas = document.createElement(\"canvas\"); snowcanvas.id = \"snowfall\"; snowcanvas.width = window.innerWidth; snowcanvas.height = document.body.clientHeight; snowcanvas.setAttribute(\"style\", \"position:absolute; top: 0; left: 0; z-index: 1; pointer-events: none;\"); document.getElementsByTagName(\"body\")[0].appendChild(snowcanvas); this.canvas = snowcanvas; this.ctx = snowcanvas.getContext(\"2d\"); /* 窗口大小改变的处理 */ window.onresize = function() { snowcanvas.width = window.innerWidth; /* snowcanvas.height = window.innerHeight */ }}/* 雪运动对象 */function flakeMove(canvasWidth, canvasHeight, flakeSize, fallSpeed) { this.x = Math.floor(Math.random() * canvasWidth); /* x坐标 */ this.y = Math.floor(Math.random() * canvasHeight); /* y坐标 */ this.size = Math.random() * flakeSize + 2; /* 形状 */ this.maxSize = flakeSize; /* 最大形状 */ this.speed = Math.random() * 1 + fallSpeed; /* 坠落速度 */ this.fallSpeed = fallSpeed; /* 坠落速度 */ this.velY = this.speed; /* Y方向速度 */ this.velX = 0; /* X方向速度 */ this.stepSize = Math.random() / 30; /* 步长 */ this.step = 0 /* 步数 */}flakeMove.prototype.update = function() { var x = this.x, y = this.y; /* 左右摆动(余弦) */ this.velX *= 0.98; if (this.velY <= this.speed) { this.velY = this.speed } this.velX += Math.cos(this.step += .05) * this.stepSize; this.y += this.velY; this.x += this.velX; /* 飞出边界的处理 */ if (this.x >= canvas.width || this.x <= 0 || this.y >= canvas.height || this.y <= 0) { this.reset(canvas.width, canvas.height) }};/* 飞出边界-放置最顶端继续坠落 */flakeMove.prototype.reset = function(width, height) { this.x = Math.floor(Math.random() * width); this.y = 0; this.size = Math.random() * this.maxSize + 2; this.speed = Math.random() * 1 + this.fallSpeed; this.velY = this.speed; this.velX = 0;};// 渲染雪花-随机形状(此处可修改雪花颜色!!!)flakeMove.prototype.render = function(ctx) { var snowFlake = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size); snowFlake.addColorStop(0, \"rgba(255, 255, 255, 0.9)\"); /* 此处是雪花颜色,默认是白色 */ snowFlake.addColorStop(.5, \"rgba(255, 255, 255, 0.5)\"); /* 若要改为其他颜色,请自行查 */ snowFlake.addColorStop(1, \"rgba(255, 255, 255, 0)\"); /* 找16进制的RGB 颜色代码。 */ ctx.save(); ctx.fillStyle = snowFlake; ctx.beginPath(); ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); ctx.fill(); ctx.restore();};/* 创建雪花-定义形状 */function createFlakes() { var maxFlake = this.maxFlake, flakes = this.flakes = [], canvas = this.canvas; for (var i = 0; i < maxFlake; i++) { flakes.push(new flakeMove(canvas.width, canvas.height, this.flakeSize, this.fallSpeed)) }}/* 画雪 */function drawSnow() { var maxFlake = this.maxFlake, flakes = this.flakes; ctx = this.ctx, canvas = this.canvas, that = this; /* 清空雪花 */ ctx.clearRect(0, 0, canvas.width, canvas.height); for (var e = 0; e < maxFlake; e++) { flakes[e].update(); flakes[e].render(ctx); } /* 一帧一帧的画 */ this.loop = requestAnimationFrame(function() { drawSnow.apply(that); });}/* 调用及控制方法 */var snow = new snowFall({maxFlake:60});snow.start(); 然后在 \\Hexo\\themes\\hexo-theme-spfk\\layout\\layout.ejs 文件里引用即可: 12<!-- 雪花特效 --><script type=\"text/javascript\" src=\"\\js\\snow.js\"></script> 如果没效果,请确认网页是否已载入JQurey,如果没有请在下雪代码之前引入JQ即可: 12<script type=\"text/javascript\" src=\"http://libs.baidu.com/jquery/1.8.3/jquery.js\"></script><script type=\"text/javascript\" src=\"http://libs.baidu.com/jquery/1.8.3/jquery.min.js\"></script> 原文链接:《分享两种圣诞节雪花特效JS代码(网站下雪效果)》 【17】添加 Fork me on GitHub 效果 效果图: 点击此处可以查看更多样式,将相应样式的代码复制到你想要放的地方就OK了,代码里的链接也要替换成你的,更多创意,比如 Follow me on CSDN ,只需要用PS改掉图片里的文字,替换掉相应链接即可 【18】添加背景动态彩带效果 样式一是鼠标点击后彩带自动更换样式,样式二是飘动的彩带: 实现方法:在 \\themes\\material-x\\layout\\layout.ejs 文件的body前面添加如下代码: 12<!-- 样式一(鼠标点击更换样式) --><script src=\"https://g.joyinshare.com/hc/ribbon.min.js\" type=\"text/javascript\"></script> 12<!-- 样式二(飘动的彩带) --><script src=\"https://g.joyinshare.com/hc/piao.js\" type=\"text/javascript\"></script> 【19】添加背景代码雨特效 新建 DigitalRain.js,写入以下代码: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657window.onload = function(){ //获取画布对象 var canvas = document.getElementById(\"canvas\"); //获取画布的上下文 var context =canvas.getContext(\"2d\"); var s = window.screen; var W = canvas.width = s.width; var H = canvas.height; //获取浏览器屏幕的宽度和高度 //var W = window.innerWidth; //var H = window.innerHeight; //设置canvas的宽度和高度 canvas.width = W; canvas.height = H; //每个文字的字体大小 var fontSize = 12; //计算列 var colunms = Math.floor(W /fontSize); //记录每列文字的y轴坐标 var drops = []; //给每一个文字初始化一个起始点的位置 for(var i=0;i<colunms;i++){ drops.push(0); } //运动的文字 var str =\"WELCOME TO WWW.ITRHX.COM\"; //4:fillText(str,x,y);原理就是去更改y的坐标位置 //绘画的函数 function draw(){ context.fillStyle = \"rgba(238,238,238,.08)\";//遮盖层 context.fillRect(0,0,W,H); //给字体设置样式 context.font = \"600 \"+fontSize+\"px Georgia\"; //给字体添加颜色 context.fillStyle = [\"#33B5E5\", \"#0099CC\", \"#AA66CC\", \"#9933CC\", \"#99CC00\", \"#669900\", \"#FFBB33\", \"#FF8800\", \"#FF4444\", \"#CC0000\"][parseInt(Math.random() * 10)];//randColor();可以rgb,hsl, 标准色,十六进制颜色 //写入画布中 for(var i=0;i<colunms;i++){ var index = Math.floor(Math.random() * str.length); var x = i*fontSize; var y = drops[i] *fontSize; context.fillText(str[index],x,y); //如果要改变时间,肯定就是改变每次他的起点 if(y >= canvas.height && Math.random() > 0.99){ drops[i] = 0; } drops[i]++; } }; function randColor(){//随机颜色 var r = Math.floor(Math.random() * 256); var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); return \"rgb(\"+r+\",\"+g+\",\"+b+\")\"; } draw(); setInterval(draw,35);}; 在主题文件的相关css文件中(以 Material X 1.2.1 主题为例,在\\themes\\material-x-1.2.1\\source\\less\\_main.less 文件末尾)添加以下代码: 12345678910canvas { position: fixed; right: 0px; bottom: 0px; min-width: 100%; min-height: 100%; height: auto; width: auto; z-index: -1;} 然后在主题的 layout.ejs 文件中引入即可: 123<!-- 数字雨 --><canvas id=\"canvas\" width=\"1440\" height=\"900\" ></canvas><script type=\"text/javascript\" src=\"/js/DigitalRain.js\"></script> 最终效果: 代码来源:http://www.lxl8800.cn/Main/Resource 【20】自定义一个不使用主题模板渲染的独立页面     有时候我们需要新建一个独立的页面,这个页面不使用主题的渲染,具有自己独立的样式,可以放一些自己的作品,相册什么的,以下就介绍这种独立页面的实现方法。 方法一:     使用 Hexo 提供的跳过渲染配置,在博客根目录的配置文件 _config.yml 里找到 skip_render 关键字,在后面添加想要跳过渲染的页面,比如我们创建 \\source\\about\\index.html, 配置文件填写:skip_render: about\\**,那么就表示 \\source\\about 里所有的文件将跳过渲染,里面的文件将会被直接复制到 public 文件夹,此时就会得到一个独立的 about 页面;官方文档:https://hexo.io/docs/configuration 方法二:     在文章头部的 Front-matter 里添加配置 layout: false 来跳过渲染配置,比如我们要使 about 页面跳过渲染,创建 \\source\\about\\index.md,将这个页面的相关 HTML 代码写进.md文件并保存,然后在 index.md 的头部写入: 123456789---layout: false---{% raw %}这里是 HTML 代码{% endraw %} PS:Front-matter 是 .md 文件最上方以 — 分隔的区域,用于指定个别文件的变量,官方文档:https://hexo.io/docs/front-matter 效果可以对比我的博客主页和关于页面 【21】更改本地预览端口号hexo博客在执行 hexo s 进行本地预览的时候,默认端口号是4000,当该端口号被占用时会报错 Error: listen EADDRINUSE 0.0.0.0:4000 ,此时可以关闭占用该端口的进程,也可以更换端口号,更换端口号可以通过以下两种方法实现: 方法一:在根目录的 _config.yml 配置文件内加上如下代码更改 hexo s 运行时的端口号: 1234server: port: 5000 compress: true header: true 方法二:通过 hexo server -p 5000 命令来指定端口,这种方法只是本次执行有效 未完待续……","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"},{"name":"主题个性化","slug":"主题个性化","permalink":"https://www.itrhx.com/tags/主题个性化/"},{"name":"Material X","slug":"Material-X","permalink":"https://www.itrhx.com/tags/Material-X/"},{"name":"spfk","slug":"spfk","permalink":"https://www.itrhx.com/tags/spfk/"}]},{"title":"Markdown 语法&技巧总结","slug":"A03-markdown","date":"2018-08-25T09:57:16.879Z","updated":"2020-03-14T05:26:44.635Z","comments":true,"path":"2018/08/25/A03-markdown/","link":"","permalink":"https://www.itrhx.com/2018/08/25/A03-markdown/","excerpt":"","text":"在写博客的时候,我们不希望都是千篇一律的没有色彩,多了解一些 Markdown 语法技巧有利于丰富我们的博客,看起来更有 feel ! – 插入图片 如果你使用 MarkdownPad 的话就比较方便,可以直接选择插入本地图片或者是网络图片,实质是通过以下代码实现的,小括号里面就是你的图片地址,中括号里面是图片的替代文字,比如上面的图片代码如下:1![车](https://cdn.jsdelivr.net/gh/TRHX/ImageHosting/ITRHX-PIC/A03/01.jpg) – 插入音乐 打开网页版网易云音乐,选择你准备插入的音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,选择好尺寸后,复制底下的HTML代码 然后将此HTML代码粘贴到你想要放的地方,可自行调节播放器的大小,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐,比如See You Again (中英文版) - 罗艺恒这首歌曲代码如下: 1<iframe frameborder=\"no\" border=\"0\" marginwidth=\"0\" marginheight=\"0\" width=330 height=86 src=\"//music.163.com/outchain/player?type=2&id=32405683&auto=1&height=66\"></iframe> – 插入视频 高考毕业了我们为下一届的学弟学妹们录制高考加油视频,我担任后期制作,在这里就以该视频为例٩(๑❛ᴗ❛๑)۶,在腾讯视频播放页面找到分享按钮,复制该视频的通用代码(其他视频播放平台也一样),粘贴到文章中对应位置即可,可根据情况调整视频播放器的大小 1<iframe frameborder=\"0\" width=\"840\" height=\"500\" src=\"https://v.qq.com/txp/iframe/player.html?vid=x0643zvgtf7\" allowFullScreen=\"true\"></iframe> 未完待续……","categories":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/categories/Markdown/"}],"tags":[{"name":"Markdown","slug":"Markdown","permalink":"https://www.itrhx.com/tags/Markdown/"},{"name":"技巧","slug":"技巧","permalink":"https://www.itrhx.com/tags/技巧/"}]},{"title":"使用 Github Pages 和 Hexo 搭建自己的独立博客","slug":"A02-hexo-blog","date":"2018-08-15T13:34:58.325Z","updated":"2020-03-14T05:24:47.844Z","comments":true,"path":"2018/08/15/A02-hexo-blog/","link":"","permalink":"https://www.itrhx.com/2018/08/15/A02-hexo-blog/","excerpt":"","text":"– 前言 首先感谢您能访问我的博客:TRHX’S BLOG 这是一篇有关如何使用 Github Pages 和 Hexo 搭建属于自己独立博客的详尽教程,本人是软件工程专业本科生,目前只学习了C和C++编程语言,对网站开发的有关知识几乎为零,这也是我搭建好自己的博客之后写的第一篇博客,刚开始搭建博客的时候自己也是网上各种百度,由于自己属于小白那种,历经了千辛万苦才弄好,所以借这个机会写一篇小白真正能看懂的博客搭建教程,教你一步一步走向成功的彼岸! 推荐文章: 《我为什么写博客》 (By 知明所以) 《为什么你应该(从现在开始就)写博客》 (By 刘未鹏 | Mind Hacks) – 入门 Github Pages Github Pages可以被认为是用户编写的、托管在github上的静态网页。使用Github Pages可以为你提供一个免费的服务器,免去了自己搭建服务器和写数据库的麻烦。此外还可以绑定自己的域名。 Hexo Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。 – 安装 Node.js点击此处访问官网,按需下载相应版本,默认安装可以了 注:本人在安装过程中出现了Warning 1909,无法创建快捷方式,这种情况很少出现,如果在安装过程中也有这种情况请参考百度文库(win10系统实测可行):《Win7安装程序警告1909无法创建快捷方式》 – 安装 Git点击此处访问官网,按需下载相应版本,默认安装即可参考资料:《如何在windows下安装GIT》 (By 俊雨廷休) 《Pro Git(中文版)》 – 检验软件是否安装成功同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI Here 和 Git Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行123$ git --version$ node -v$ npm -v – Hexo 安装选择一个磁盘,新建一个文件夹,自己重命名文件夹(如:我的文件夹为:E\\TRHX_Blog),博客相关文件将储存在此文件夹下,在该文件夹下右键鼠标,点击 Git Bash Here,输入以下 npm 命令即可安装,第一个命令表示安装 hexo,第二个命令表示安装 hexo 部署到 git page 的 deployer,如图所示即为安装成功12$ npm install hexo-cli -g$ npm install hexo-deployer-git --save – Hexo 初始化配置在刚才新建的文件夹里面再次新建一个 Hexo 文件夹(如:我的文件夹为:E\\TRHX_Blog\\Hexo),进入该 Hexo 文件夹右键鼠标,点击 Git Bash Here,输入以下命令,如图所示则安装成功1$ hexo init Hexo 安装完成后,将会在指定文件夹中新建所需要的文件,Hexo 文件夹下的目录如下: – 本地查看效果执行以下命令,执行完即可登录 http://localhost:4000/ 查看效果12$ hexo generate$ hexo server 显示以下信息说明操作成功:1INFO Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop. 登录 http://localhost:4000/ 查看效果: – 将博客部署到 Github Pages 上到目前为止,我们的本地博客就成功搭建了,但是现在我们只能通过本地连接查看博客,我们要做的是让其他人也能够访问我们的博客,这就需要我们将博客部署到Github Pages上 一、注册 Github 账户:点击此处访问 Github 官网,点击 Sign Up 注册账户 二、创建项目代码库:点击 New repository 开始创建,步骤及注意事项见图: 三、配置 SSH 密钥:只有配置好 SSH 密钥后,我们才可以通过 git 操作实现本地代码库与 Github 代码库同步,在你第一次新建的文件夹里面(如:我的文件夹为:E\\TRHX_Blog) Git Bash Here 输入以下命令:12$ ssh-keygen -t rsa -C "your email@example.com"//引号里面填写你的邮箱地址,比如我的是tanrenhou@126.com 之后会出现:123Generating public/private rsa key pair.Enter file in which to save the key (/c/Users/you/.ssh/id_rsa)://到这里可以直接回车将密钥按默认文件进行存储 然后会出现:123Enter passphrase (empty for no passphrase)://这里是要你输入密码,其实不需要输什么密码,直接回车就行Enter same passphrase again: 接下来屏幕会显示:123456Your identification has been saved in /c/Users/you/.ssh/id_rsa.Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.The key fingerprint is:这里是各种字母数字组成的字符串,结尾是你的邮箱The key's randomart image is:这里也是各种字母数字符号组成的字符串 运行以下命令,将公钥的内容复制到系统粘贴板上1$ clip < ~/.ssh/id_rsa.pub 四、在 GitHub 账户中添加你的公钥 1.登陆 GitHub,进入 Settings: 2.点击 SSH and GPG Keys: 3.选择 New SSH key: 4.粘贴密钥: 五、测试 输入以下命令:注意:git@github.com不要做任何更改!1$ ssh -T git@github.com 之后会显示: 输入 yes 后会显示:此时表示设置正确 六、配置 Git 个人信息 Git 会根据用户的名字和邮箱来记录提交,GitHub 也是用这些信息来做权限的处理,输入以下命令进行个人信息的设置,把名称和邮箱替换成你自己的,名字可以不是 GitHub 的昵称,但为了方便记忆,建议与 GitHub 一致12$ git config --global user.name "此处填你的用户名"$ git config --global user.email "此处填你的邮箱" 到此为止 SSH Key 配置成功,本机已成功连接到 Github – 将本地的 Hexo 文件更新到 Github 的库中一、登录 Github 打开自己的项目 yourname.github.io 二、鼠标移到 Clone or download 按钮,选择 Use SSH 三、一键复制地址 四、打开你创建的 Hexo 文件夹(如:E:\\TRHX_Blog\\Hexo),右键用记事本(或者Notepad++、Vs Code等)打开该文件夹下的 _config.yml 文件 五、按下图修改 _config.yml 文件并保存 六、在 Hexo 文件夹下分别执行以下命令12$ hexo g$ hexo d 或者直接执行1$ hexo g -d 执行完之后会让你输入你的 Github 的账号和密码,如果此时报以下错误,说明你的 deployer 没有安装成功1ERROR Deployer not found: git 需要执行以下命令再安装一次:1npm install hexo-deployer-git --save 再执行 hexo g -d,你的博客就会部署到 Github 上了 七、访问博客 你的博客地址:https://你的用户名.github.io,比如我的是:https://trhx.github.io ,现在每个人都可以通过此链接访问你的博客了 – 如何在博客上发表文章博客已经成功搭建了,但是我们该怎么写博客呢? 一、新建一个空文章,输入以下命令,会在项目 \\Hexo\\source\\_posts 中生成 文章标题.md 文件,文章标题根据需要命名1$ hexo n "文章标题" 也可以直接在 \\Hexo\\source\\_posts 目录下右键鼠标新建文本文档,改后缀为 .md 即可,这种方法比较方便 二、用编辑器编写文章 md 全称 Markdown, Markdown 是 2004 年由 John Gruberis 设计和开发的纯文本格式的语法,非常的简单实用,常用的标记符号屈指可数,几分钟即可学会, .md 文件可以使用支持 Markdown 语法的编辑器编辑,然后将写好的文章(.md文件)保存到 \\Hexo\\source\\_posts 文件夹下即可推荐 Windows 上使用 MarkdownPad2 或者 小书匠 编辑器,macOS 上使用 Mou 编辑器,Linux 上使用 Remarkable 编辑器,Web 端上使用 简书 ,另外可以参考我的另一篇文章:《主流 Markdown 编辑器推荐》当我们用编辑器写好文章后,可以使用以下命令将其推送到服务器上12$ hexo g$ hexo d或者将两个命令合二为一输入以下命令:1$ hexo d -g现在访问你的博客就可以看见写好的文章啦!参考资料:《10款流行的Markdown编辑器》 (By xiaoxiao_engineer) 《献给写作者的 Markdown 新手指南》 (By 简书) 《认识与入门 Markdown》 (By Te_Lee) 《markdown简明语法》 (By 不如) 《markdown基本语法》 (By 高鸿祥) 《Markdown 公式指导手册》 (By Harries)# – 如何为博客更换自己喜欢的主题 博客也搭建好了,文章也会写了,但是!!!默认的主题并不喜欢怎么办?现在,我们就来为自己的博客更换自己喜欢的主题 点击此处进入 Hexo 官网的主题专栏,我们可以看见有许多的主题供我们选择 我们要做的就是把主题克隆过来,在此我们以主题 Aero-Dual 为例,点进去我们就可以看见该主题作者的博客,鼠标滑到底,我们可以看见 Theme By Levblanc 的字样(其他主题类似),点击作者 Levblanc ,页面就会跳转到该主题所有的相关文件在 Github 上的地址,复制该地址 再打开 Hexo 文件夹下的 themes 目录(如:E:\\TRHX_Blog\\Hexo\\themes),右键 Git Bash Here,输入以下命令:1$ git clone 此处填写你刚才复制的主题地址 比如要安装 Aero-Dual 主题,则输入命令:1$ git clone https://github.com/levblanc/hexo-theme-aero-dual 等待下载完成后即可在 themes 目录下生成 hexo-theme-aero-dual 文件夹,然后打开 Hexo 文件夹下的配置文件 _config.yml ,找到关键字 theme,修改参数为:theme:hexo-theme-aero-dual (其他主题修改成相应名称即可),再次注意冒号后面有一个空格! 返回 Hexo 目录,右键 Git Bash Here ,输入以下命令开始部署主题:12$ hexo g $ hexo s 此时打开浏览器,访问 http://localhost:4000/ 就可看见我们的主题已经更换了,如果感觉效果满意,我们就可以把它部署到Github上了 打开 Hexo 文件夹,右键 Git Bash Here ,输入以下命令:123$ hexo clean //该命令的作用是清除缓存,若不输入此命令,服务器有可能更新不了主题$ hexo g -d 此时访问自己的博客即可看见更换后的主题,但我们仍然需要对主题的相关配置进行修改,比如网站标题,图标等等,Hexo 中有两份主要的配置文件,名称都是 _config.yml ,它们均是用于站点配置使用的。其中,一份位于站点根目录下(比如我的:E:\\TRHX_Blog\\Hexo\\_config.yml),主要包含 Hexo 本身整站的配置;另一份位于主题目录下(比如我的:E:\\TRHX_Blog\\Hexo\\themes\\hexo-theme-aero-dual\\_config.yml),这份配置由主题作者提供,主要用于配置主题相关的选项,一般 _config.yml 文件里都有相关注释,按需修改即可 参考资料:《有哪些好看的 Hexo 主题?》 (知乎) 《Hexo | 配置》 (Hexo官方文档) 《hexo常用命令笔记》 (By 小弟调调) – 为你的 Hexo 博客配置个性域名本人在配置域名的时候问题百出,百度的各种方法都不管用,打开网站总是 404,可能是我太笨了 o(╥﹏╥)o ,不过好在后来终于解决了这个问题 首先我们要购买域名,阿里云,腾讯云都可以,也不贵,一年几十块钱,最便宜几块钱也能买到,以阿里云为例,我购买的域名是 itrhx.com,购买过程就不赘述了,选择阿里云的解析平台,来到阿里云的管理控制台,点击进入域名解析列表或者直接点击域名后面的解析 方法一:点击添加记录,需要添加两个记录,两个记录类型都是 CNAME ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都是填你自己的博客地址(比如我的是:trhx.github.io),保存之后域名解析就完成了!方法二:两个记录类型为 A ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都为博客的 IP 地址,IP 地址可以 cmd 中输入 ping 你的博客地址 获得(比如我的:ping trhx.github.io),保存之后域名解析就完成了!有关解析记录类型的区别可以参考《域名解析中A记录、CNAME、MX记录、NS记录的区别和联系》 为了使 GitHub 接收我们的域名,还需要在博客的根目录下添加一个名为 CNAME 的文件(注意不要加.txt,没有任何后缀名!),这个文件放到 Hexo 文件夹的 source 里面,(比如我的是:E:\\TRHX_Blog\\Hexo\\source),文件里面填写你的域名(加不加www都行),比如要填写我的域名,文件里面就写:www.itrhx.com 或者 itrhx.com,经过以上操作,别人就可以通过 www.itrhx.com 、itrhx.com 、trhx.github.io 三个当中任意一个访问我的博客了!你的也一样! 有关加不加www的问题有以下区别: 如果你填写的是没有www的,比如 itrhx.com,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 https://itrhx.com 如果你填写的是带www的,比如 www.itrhx.com ,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 http://www.itrhx.com 如果你在其他平台购买域名,或者选择 DNSPod 等其他域名解析,操作方法大同小异,遇到问题可自行百度解决! 参考资料:《推荐几家域名注册服务商》 (By Jelly Bool) 《盘点十大免费DNS域名解析服务:稳定、可靠》 – 结语一顿操作下来虽然有点儿累,但看见拥有了自己的博客还是非常有成就感的,人生就是需要折腾,那么现在就开始你的创作之旅吧!文章的不断积累,你会从中受益很多的!另外,这是一篇小白写的适用于小白的博客搭建教程,比较详细,有这方面基础的可以百度有简略一点儿的教程,文中如有错误还请大佬指出改正!文中涉及参考资料如有侵权请联系我删除!","categories":[{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/categories/Hexo/"}],"tags":[{"name":"Github Pages","slug":"Github-Pages","permalink":"https://www.itrhx.com/tags/Github-Pages/"},{"name":"Hexo","slug":"Hexo","permalink":"https://www.itrhx.com/tags/Hexo/"}]},{"title":"Hello World!","slug":"A01-hello-world","date":"2018-08-10T09:38:00.000Z","updated":"2019-09-09T14:13:26.239Z","comments":true,"path":"2018/08/10/A01-hello-world/","link":"","permalink":"https://www.itrhx.com/2018/08/10/A01-hello-world/","excerpt":"","text":"人类的幸福和欢乐在于奋斗,而最有价值的是为理想而奋斗! ——— 苏格拉底 Human happiness and joy lie in struggle, and what is most valuable is striving for ideals! ——— Socrates","categories":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/categories/BLOG/"}],"tags":[{"name":"BLOG","slug":"BLOG","permalink":"https://www.itrhx.com/tags/BLOG/"}]}]} \ No newline at end of file diff --git a/friends/index.html b/friends/index.html index 76fe77f6f..351a655b3 100644 --- a/friends/index.html +++ b/friends/index.html @@ -475,7 +475,7 @@ @@ -573,7 +573,7 @@
    - +

    xaoxuu’s blog

    @@ -611,7 +611,7 @@
    - +

    番茄酱の萌化小屋

    @@ -632,7 +632,7 @@
    - +

    attack204

    @@ -748,7 +748,7 @@
    - +

    JerryC

    @@ -953,7 +953,7 @@
    - +

    Yinux's Blog

    @@ -1253,7 +1253,7 @@
    - +

    Emil’s blog

    @@ -1305,23 +1305,17 @@
    - +
    - +
    -

    空梦博客

    +

    优客-blog

    -

    Web

    - -

    Java

    - -

    框架

    - -

    码农

    +

    一定要比从前的自己更加优秀!

    diff --git a/index.html b/index.html index 2616dfc0a..76756fac5 100644 --- a/index.html +++ b/index.html @@ -527,12 +527,6 @@ - - - - - - @@ -692,6 +686,12 @@ + + + + + + diff --git a/page/2/index.html b/page/2/index.html index b66242312..9b66e308c 100644 --- a/page/2/index.html +++ b/page/2/index.html @@ -868,14 +868,14 @@
    - +

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    diff --git a/search.xml b/search.xml index 3f4d54f64..1eb2edbfe 100644 --- a/search.xml +++ b/search.xml @@ -1,1732 +1,774 @@ - - - - - - 用 VPS 搭建一个自己的 SSR 服务器 - - /2020/01/10/A61-build-a-SSR-server-with-VPS/ - -

    俗话说得好:预先善其事,必先利其器,作为一个程序员,经常会用到 GitHub、Google、Stack Overflow 啥的,由于国内政策原因,想要访问国外网站就得科学上网,最常见的工具就是 ShadowsocksR,又被称为酸酸乳、SSR、小飞机,目前市面上有很多很多的机场,价格也不是很高,完全可以订阅别人的,但是订阅别人的,数据安全没有保障,有可能你的浏览历史啥的别人都能掌握,别人也有随时跑路的可能,总之,只有完全属于自己的东西才是最香的!


    购买 VPS

    VPS(Virtual Private Server)即虚拟专用服务器技术,在购买 VPS 服务器的时候要选择国外的,推荐 Vultr,国际知名,性价比比较高,最低有$2.5/月、$3.5/月的,个人用的话应该足够了。


    01

    点击链接注册 Vultr 账号:https://www.vultr.com/?ref=8367048,目前新注册用户充值10刀可以赠送50刀,注册完毕之后来到充值页面,最低充值10刀,可以选择支付宝或者微信支付。


    02

    充值完毕之后,点击左侧 Products,选择服务器,一共有16个地区的,选择不同地区的服务器,最后的网速也有差别,那如何选择一个速度最优的呢?很简单,你可以一次性选择多个服务器,都部署上去,搭建完毕之后,测试其速度,选择最快的,最后再把其他的都删了,可能你会想,部署多个,那费用岂不是很贵,这里注意,虽然写的是多少钱一个月,而实际上它是按照小时计费的,从你部署之后开始计费,$5/月 ≈ $0.00694/小时,你部署完毕再删掉,这段时间的费用很低,可以忽略不计,一般来说,日本和新加坡的比较快一点,也有人说日本和新加坡服务器的端口封得比较多,容易搭建失败,具体可以自己测试一下,还有就是,只有部分地区的服务器有$2.5/月、$3.5/月的套餐,其中$2.5/月的只支持 IPv6,可以根据自己情况选择,最后操作系统建议选择 CentOS 7 x64 的,不然有可能搭建失败,后面还有个 Enable IPv6 的选项,对 IPv6 有需求的话可以勾上,其他选项就可以不用管了。


    03

    04

    部署成功后,点 Server Details 可以看到服务器的详细信息,其中有 IP、用户名、密码等信息,后面搭建 SSR 的时候会用到,此时你可以 ping 一下你的服务器 IP,如果 ping 不通的话,可以删掉再重新开一个服务器。


    05

    搭建 SSR

    我们购买的是虚拟的服务器,因此需要工具远程连接到 VPS,如果是 Mac/Linux 系统,可以直接在终端用 SSH 连接 VPS:

    1
    ssh root@你VPS的IP -p 22 (22是你VPS的SSH端口)

    如果是 Windows 系统,可以用第三方工具连接到 VPS,如:Xshell、Putty 等,可以百度下载,以下以 Xshell 为例:

    点击文件,新建会话,名称可以随便填,协议为 SSH,主机为你服务器的 IP 地址,点击确定,左侧双击这个会话开始连接,最开始会出现一个 SSH安全警告,点击接受并保存即可,然后会让你输入服务器的用户名和密码,直接在 Vultr 那边复制过来即可,最后看到 [root@vultr ~]# 字样表示连接成功。


    06

    07

    08

    09

    10

    连接成功后执行以下命令开始安装 SSR:

    1
    wget --no-check-certificate https://freed.ga/github/shadowsocksR.sh; bash shadowsocksR.sh

    如果提示 wget :command not found,可先执行 yum -y install wget,再执行上述命令即可。

    执行完毕后会让你设置 SSR 连接密码和端口,然后按任意键开始搭建。


    11

    搭建成功后会显示你服务器 IP,端口,连接密码,协议等信息,这些信息要记住,后面使用 ShadowsocksR 的时候要用到。


    12

    安装锐速

    由于我们购买的服务器位于国外,如果遇到上网高峰期,速度就会变慢,而锐速就是一款专业的连接加速器,可以充分利用服务器带宽,提升带宽吞吐量,其他还有类似的程序如 Google BBR 等,可以自行比较其加速效果,以下以操作系统为 CentOS 6&7 锐速的安装为例。

    如果你服务器操作系统选择的是 CentOS 6 x64,则直接执行以下命令,一直回车即可:

    1
    wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/hombo125/doubi/master/appex.sh && bash appex.sh install '2.6.32-642.el6.x86_64'

    如果你服务器操作系统选择的是 CentOS 7 x64,则需要先执行以下命令更换内核:

    1
    wget --no-check-certificate -O rskernel.sh https://raw.githubusercontent.com/hombo125/doubi/master/rskernel.sh && bash rskernel.sh

    如下图所示表示内核更换完毕,此时已经断开与服务器的连接,我们需要重新连接到服务器,再执行后面的操作:


    13

    重新连接到服务器后,再执行以下命令:

    1
    yum install net-tools -y && wget --no-check-certificate -O appex.sh https://raw.githubusercontent.com/0oVicero0/serverSpeeder_Install/master/appex.sh && bash appex.sh install

    然后一直回车即可,系统会自动安装锐速。


    14

    15

    出现以下信息表示安装成功:


    16

    使用 SSR

    常见的工具有 ShadowsocksR、SSTap(原本是个游戏加速器,现在已经停止维护,但 GitHub 上仍然可以找到)等。

    Shadowsocks 官网:https://shadowsocks.org/
    ShadowsocksR 下载地址:https://github.com/Anankke/SSRR-Windows
    SSTap GitHub 地址:https://github.com/FQrabbit/SSTap-Rule

    不管什么工具,用法都是一样的,添加一个新的代理服务器,服务器 IP、端口、密码、加密方式等等这些信息保持一致就行了。然后就可以愉快地科学上网了!


    17

    18

    多端口配置

    经过以上步骤我们就可以科学上网了,但是目前为止只有一个端口,只能一个人用,那么如何实现多个端口多人使用呢?事实上端口、密码等信息是储存在一个叫做 shadowsocks.json 文件里的,如果要添加端口或者更改密码,只需要修改此文件即可。

    连接到自己的 VPS,输入以下命令,使用 vim 编辑文件:vi /etc/shadowsocks.json

    原文件内容大概如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "server": "0.0.0.0",
    "server_port": 8686,
    "server_ipv6": "::",
    "local_address": "127.0.0.1",
    "local_port": 1081,
    "password":"SSR12345",
    "timeout": 120,
    "udp_timeout": 60,
    "method": "aes-256-cfb",
    "protocol": "auth_sha1_v4_compatible",
    "protocol_param": "",
    "obfs": "http_simple_compatible",
    "obfs_param": "",
    "dns_ipv6": false,
    "connect_verbose_info": 1,
    "redirect": "",
    "fast_open": false,
    "workers": 1
    }

    增加端口,我们将其修改为如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    {
    "server": "0.0.0.0",
    "server_ipv6": "::",
    "local_address": "127.0.0.1",
    "local_port": 1081,
    "port_password":
    {
    "8686":"SSR1",
    "8687":"SSR2",
    "8688":"SSR3",
    "8689":"SSR4",
    "8690":"SSR5"
    },
    "timeout": 120,
    "udp_timeout": 60,
    "method": "aes-256-cfb",
    "protocol": "auth_sha1_v4_compatible",
    "protocol_param": "",
    "obfs": "http_simple_compatible",
    "obfs_param": "",
    "dns_ipv6": false,
    "connect_verbose_info": 1,
    "redirect": "",
    "fast_open": false,
    "workers": 1
    }

    也就是删除原来的 server_portpassword 这两项,然后增加 port_password 这一项,前面是端口号,后面是密码,注意不要把格式改错了!!!修改完毕并保存!!!

    接下来配置一下防火墙,同样的,输入以下命令,用 vim 编辑文件:vi /etc/firewalld/zones/public.xml

    初始的防火墙只开放了最初配置 SSR 默认的那个端口,现在需要我们手动加上那几个新加的端口,注意:一个端口需要复制两行,一行是 tcp,一行是 udp。

    原文件内容大概如下:

    1
    2
    3
    4
    5
    6
    7
    8
    <?xml version="1.0" encoding="utf-8"?>
    <zone>
    <short>Public</short>
    <service name="dhcpv6-client"/>
    <service name="ssh"/>
    <port protocol="tcp" port="8686"/>
    <port protocol="udp" port="8686"/>
    </zone>

    修改后的内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="utf-8"?>
    <zone>
    <short>Public</short>
    <service name="dhcpv6-client"/>
    <service name="ssh"/>
    <port protocol="tcp" port="8686"/>
    <port protocol="udp" port="8686"/>
    <port protocol="tcp" port="8687"/>
    <port protocol="udp" port="8687"/>
    <port protocol="tcp" port="8688"/>
    <port protocol="udp" port="8688"/>
    <port protocol="tcp" port="8689"/>
    <port protocol="udp" port="8689"/>
    <port protocol="tcp" port="8690"/>
    <port protocol="udp" port="8690"/>
    </zone>

    修改完毕并保存,最后重启一下 shadowsocks,然后重新载入防火墙即可,两条命令如下:

    1
    /etc/init.d/shadowsocks restart
    1
    firewall-cmd --reload

    完成之后,我们新加的这几个端口就可以使用了

    另外还可以将配置转换成我们常见的链接形式,如:ss://xxxxxssr://xxxxx,其实这种链接就是把 IP,端口,密码等信息按照一定的格式拼接起来,然后经过 Base64 编码后实现的,有兴趣或者有需求的可以自行百度。


    扩展命令

    SSR 常用命令:
    启动:/etc/init.d/shadowsocks start
    停止:/etc/init.d/shadowsocks stop
    重启:/etc/init.d/shadowsocks restart
    状态:/etc/init.d/shadowsocks status
    卸载:./shadowsocks-all.sh uninstall
    更改配置参数:vim /etc/shadowsocks-r/config.json

    ]]>
    - - - - - VPS - - - - - - - VPS - - SSR - - - -
    - - - - - 2019年总结【跨越今天,更不平凡】 - - /2019/12/31/A60-2019-summary/ - -
    1

    还记得小时候写作文,畅想2020会怎样怎样,光阴似箭,2020真的来了,度过了艰难的考试周,抽了个晚上,回想了一下,决定写一写总结吧,似乎以前都没写过呢,那干脆连带2017、2018也写写吧,重点写一写2019的,以后争取每年都做一下总结。


    【2017】

    2017年高三,上半年就不用说了,所有高三考生都一个样吧,下半年考进了武汉的某二本院校,软件工程专业,现在回想起来,当时把时间浪费得太多了,最开始加了一个部门,后来退了(事实上啥也学不到,浪费时间 ),然后除了完成学校的课程以外,其他啥也没搞,剩下的时间基本上全拿来骑车了,从高一开始就热爱单车运动,刚上大学肯定得放飞自我了,没课的时候就天天和学长到处跑,都快把武汉跑了个遍了,当时还定了个计划,大学四年骑车去一次西藏或者青海湖,其他的什么都没想,也没有对以后具体干哪方面做过规划,这一年收获最多的应该就是路上的风景了。


    2

    【2018】

    2018上半年,大一下学期,学习方面就过了个英语四级,然后依旧热衷于我的单车,暑假的时候疯狂了一把,7天干了700多公里,从学校骑回家了,那个时候正是热的时候,白天基本上在三十度,从武汉往西边走,后面全是爬山,上山爬不动,下山刹不住,路上也遇到了不少牛逼人物,有徒步西藏的,有环游中国的,直播平台有好几十万粉丝的……遇到的人都很善良,很硬汉,这次经历从某种程度上来说也是一次成长吧,一次很有意义的骑行。

    下半年,也就是大二开始,才慢慢开始重视专业知识的学习,大二上学期搭建了个人博客,开始尝试写博客,其实就是把博客当做笔记吧,记性不好,学了的东西容易忘记,忘记了可以经常翻自己博客再复习复习,自己踩过的坑也记录记录,后来没想到有些文章访问量还挺高的,在博客搭建方面也帮到了一些网友,最重要的是结识了不少博友,有各行各业的大佬,下半年也定了方向,开始专注Python的学习,从此开始慢慢熬夜,也渐渐地不怎么出去骑车了。


    3

    4

    【2019】

    2019 总的来说,还比较满意吧,主要是感觉过得很充实,大三基本上每天一整天都是上机课,没有太多时间搞自己的,自己倾向于Python、网络爬虫、数据分析方面,然而这些课程学校都没有,每天晚上以及周六周日都是自己在学,找了不少视频在看,有时候感觉自己还是差点火候,感觉一个简单的东西人家看一遍就会,但是我要看好几遍,不管怎样,我还是相信勤能补拙的。

    【学习方面】
    • [√] 通过软考中级软件设计师
    • [√] 成为入党积极分子
    • [√] 学校大课基金结题
    • 英语六级未通过
    • 国家专利未通过
    【看完或者大部分看完的书籍】
    • [√] 《软件设计师考试》
    • [√] 《Python 编程从入门到实践》
    • [√] 《Python 编程从零基础到项目实战》
    • [√] 《Python3 网络爬虫开发实战》
    • [√] 《Python 网络爬虫从入门到实践》
    • [√] 《精通 Python 爬虫框架 Scrapy》
    • [√] 《Python 程序员面试宝典(算法+数据结构)》
    • [√] 《Selenium 自动化测试 — 基于 Python 语言》
    • [√] 《重构,改善既有代码的设计》
    【生活方面】

    暑假受家族前辈的邀请,为整个姓氏家族编写族谱,感觉这是今年收获最大的一件事情吧,当时背着电脑跟着前辈下乡,挨家挨户统计资料,纯手工录入电脑(感觉那是我活了二十年打字打得最多的一个月,祖宗十八代都搞清楚了),最后排版打印成书,一个月下来感受到了信息化时代和传统文化的碰撞,见了很多古书,古迹,当然还领略到了古繁体字的魅力,前辈一路上给我讲述了很多书本上学不到的东西,一段很有意义的体验,感触颇深。

    个人爱好上面,今年就基本上没有骑车了,没有经常骑车,开学骑了两次就跟不上别人了,后面就洗干净用布遮起来放在寝室了,按照目前情况来看,多半是要“退役”了,不知道何时才会又一次踩上脚踏,不过偶尔还是在抖音上刷刷关注的单车大佬,看看别人的视频,看到友链小伙伴 Shan San 在今年总结也写了他一年没有跳舞了,抛弃了曾经热爱的 Breaking,真的是深有感触啊。

    有个遗憾就是大一的愿望实现不了了,恐怕大学四年也不会去西藏或者青海湖了,此处放一个到目前为止的骑行数据,以此纪念一下我的单车生涯吧。


    5
    【技术交流&实践】

    自从搭建了博客之后,认识了不少大佬,经常会去大佬博客逛逛,涨涨知识

    截止目前,个人博客 PV:4万+,UV:1万+,知乎:400+赞同,CSDN:43万+访问量,400+赞同

    此外今年第一次为开源做了一点儿微不足道的贡献,为 Hexo 博客主题 Material X 添加了文章字数统计和阅读时长的功能,提交了人生当中第一个 PR。第一次嘛,还是值得纪念一下的。


    6

    我 GitHub 上虽然有一些小绿点,但是很大一部分都是推送的博客相关的东西,剩下的有几个仓库也就是 Python 相关的了,一些实战的代码放在了上面,很多时候是拿 GitHub 围观一些牛逼代码或者资源,还需要努力学习啊!


    7

    8

    实战方面,爬虫自己也爬了很多网站,遇到一些反爬网站还不能解决,也刷了一些 Checkio 上面的题,做了题,和其他大佬相比才会发现自己的代码水平有多低,最直接的感受就是我用了很多行代码,而大神一行代码就解决了,只能说自己的水平还有很大的增进空间,新的一年继续努力吧!


    9

    【2020】

    1024 + 996 = 2020,2020注定是不平凡的一年,定下目标,努力实现,只谈技术,莫问前程!

    【计划目标】
    • 4月蓝桥杯拿奖
    • 5月通过软考高级信息系统项目管理师
    • 6月通过英语六级
    • 坚持记笔记、写博客
    • 学习 JavaScript 逆向
    • 研究网站常用反爬策略,掌握反反爬虫技术
    • 掌握两到三个主流爬虫框架
    • 加深 Python 算法和数据结构的学习
    • 学习 Python 数据可视化和数据分析
    • 做一个 Python 相关的优秀开源项目(爬虫类最好)
    • 向优秀爬虫工程师方向迈进
    • 参加 PyCon China 2020
    【计划要看的书籍】
    • 《JavaScript 从入门到精通》
    • 《Python3 反爬虫原理与绕过实战》
    • 《Python 数据可视化编程实战》
    • 《Python 数据可视化之 matplotlib 实践》
    • 《Python 数据可视化之 matplotlib 精进》
    • 《基于 Python的大数据分析基础及实战》
    1
    2
    3
    >>> pip uninstall 2019
    >>> pip install 2020
    >>> print('Live a good life, write some good code !!!')
    ]]>
    - - - - - BLOG - - - - - - - 年终总结 - - - -
    - - - - - Python3 爬虫实战 — 瓜子全国二手车 - - /2019/11/15/A59-pyspider-guazi/ - -

    爬取时间:2019-11-14
    爬取难度:★★☆☆☆☆
    请求链接:https://www.guazi.com/www/buy/
    爬取目标:爬取瓜子全国二手车信息,包括价格、上牌时间、表显里程等;保存车辆图片
    涉及知识:请求库 requests、解析库 lxml、Xpath 语法、数据库 MongoDB 的操作
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/guazi
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】提取所有二手车详情页URL

    分析页面,按照习惯,最开始在 headers 里面只加入 User-Agent 字段,向主页发送请求,然而返回的东西并不是主页真正的源码,因此我们加入 Cookie,再次发起请求,即可得到真实数据。

    获取 Cookie:打开浏览器访问网站,打开开发工具,切换到 Network 选项卡,筛选 Doc 文件,在 Request Headers 里可以看到 Cookie 值。

    注意在爬取瓜子二手车的时候,User-Agent 与 Cookie 要对应一致,也就是直接复制 Request Headers 里的 User-Agent 和 Cookie,不要自己定义一个 User-Agent,不然有可能获取不到信息!
    01

    分析页面,请求地址为:https://www.guazi.com/www/buy/

    第一页:https://www.guazi.com/www/buy/

    第二页:https://www.guazi.com/www/buy/o2c-1/

    第三页:https://www.guazi.com/www/buy/o3c-1/

    一共有50页数据,利用 for 循环,每次改变 URL 中 o2c-1 参数里面的数字即可实现所有页面的爬取,由于我们是想爬取每台二手车详情页的数据,所以定义一个 parse_index() 函数,提取每一页的所有详情页的 URL,保存在列表 url_list

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)
    headers = {
    'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
    }


    # 获取所有二手车详情页URL
    def parse_index():
    response = requests.get(url=url, headers=headers)
    tree = etree.HTML(response.text)
    url_list = tree.xpath('//li/a[@class="car-a"]/@href')
    # print(len(url_list))
    return url_list


    if __name__ == '__main__':
    for i in range(1, 51):
    url = 'https://www.guazi.com/www/buy/o%sc-1/' % i
    detail_urls = parse_index()

    【2x00】获取二手车详细信息并保存图片

    前面的第一步我们已经获取到了二手车详情页的 URL,现在定义一个 parse_detail() 函数,向其中循环传入每一条 URL,利用 Xpath 语法匹配每一条信息,所有信息包含:标题、二手车价格、新车指导价、车主、上牌时间、表显里程、上牌地、排放标准、变速箱、排量、过户次数、看车地点、年检到期、交强险、商业险到期

    其中有部分信息可能包含空格,可以用 strip() 方法将其去掉。

    需要注意的是,上牌地对应的是一个 class="three"li 标签,有些二手车没有上牌地信息,匹配的结果将是空,在数据储存时就有可能出现数组越界的错误信息,所以这里可以加一个判断,如果没有上牌地信息,可以将其赋值为:未知。

    保存车辆图片时,为了节省时间和空间,避免频繁爬取被封,所以只保存第一张图片,同样利用 Xpath 匹配到第一张图片的地址,以标题为图片的名称,定义储存路径后,以二进制形式保存图片。

    最后整个函数返回的是一个列表 data,这个列表包含每辆二手车的所有信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    # 获取二手车详细信息
    def parse_detail(content):
    detail_response = requests.get(url=content, headers=headers)
    tree = etree.HTML(detail_response.text)

    # 标题
    title = tree.xpath('//h2[@class="titlebox"]/text()')
    # 移除字符串头尾空格
    title = [t.strip() for t in title]
    # 匹配到两个元素,只取其中一个为标题
    title = title[:1]
    # print(title)

    # 价格
    price_old = tree.xpath('//span[@class="pricestype"]/text()')
    # 移除字符串头尾空格
    price_old = [p.strip() for p in price_old]
    # 加入单位
    price_old = [''.join(price_old + ['万'])]
    # print(price_old)

    # 新车指导价
    price_new = tree.xpath('//span[@class="newcarprice"]/text()')
    # 移除字符串头尾空格
    price_new = [p.strip() for p in price_new]
    # 对字符串进行切片,只取数字多少万
    price_new = ['¥' + price_new[0].split('价')[1]]
    # print(price_new)

    # 车主
    owner = tree.xpath('//dl/dt/span/text()')
    owner = [owner[0].replace('车主:', '')]
    # print(owner)

    # 上牌时间
    spsj = tree.xpath('//li[@class="one"]/div/text()')
    # print(spsj)

    # 表显里程
    bxlc = tree.xpath('//li[@class="two"]/div/text()')
    # print(bxlc)

    # 上牌地
    spd = tree.xpath('//li[@class="three"]/div/text()')
    # 某些二手车没有上牌地,没有的将其赋值为:未知
    if len(spd) == 0:
    spd = ['未知']
    # print(spd)

    # 排放标准
    pfbz = tree.xpath('//li[@class="four"]/div/text()')
    pfbz = pfbz[:1]
    # print(pfbz)

    # 变速箱
    bsx = tree.xpath('//li[@class="five"]/div/text()')
    # print(bsx)

    # 排量
    pl = tree.xpath('//li[@class="six"]/div/text()')
    # print(pl)

    # 过户次数
    ghcs = tree.xpath('//li[@class="seven"]/div/text()')
    ghcs = [g.strip() for g in ghcs]
    ghcs = ghcs[:1]
    # print(ghcs)

    # 看车地点
    kcdd = tree.xpath('//li[@class="eight"]/div/text()')
    # print(kcdd)

    # 年检到期
    njdq = tree.xpath('//li[@class="nine"]/div/text()')
    # print(njdq)

    # 交强险
    jqx = tree.xpath('//li[@class="ten"]/div/text()')
    # print(jqx)

    # 商业险到期
    syxdq = tree.xpath('//li[@class="last"]/div/text()')
    syxdq = [s.strip() for s in syxdq]
    syxdq = syxdq[:1]
    # print(syxdq)

    # 保存车辆图片
    # 获取图片地址
    pic_url = tree.xpath('//li[@class="js-bigpic"]/img/@data-src')[0]
    pic_response = requests.get(pic_url)
    # 定义图片名称以及保存的文件夹
    pic_name = title[0] + '.jpg'
    dir_name = 'guazi_pic'
    # 如果没有该文件夹则创建该文件夹
    if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    # 定义储存路径
    pic_path = dir_name + '/' + pic_name
    with open(pic_path, "wb")as f:
    f.write(pic_response.content)

    # 将每辆二手车的所有信息合并为一个列表
    data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq
    return data


    if __name__ == '__main__':
    for i in range(1, 51):
    url = 'https://www.guazi.com/www/buy/o%sc-1/' % i
    detail_urls = parse_index()
    for detail_url in detail_urls:
    car_url = 'https://www.guazi.com' + detail_url
    car_data = parse_detail(car_url)

    【3x00】将数据储存到 MongoDB

    定义数据储存函数 save_data()

    使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 guazi,集合为 esc

    传入第二步 parse_detail() 函数返回的二手车信息的列表,依次读取其中的元素,每一个元素对应相应的信息名称

    最后调用 insert_one() 方法,每次插入一辆二手车的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    # 将数据储存到 MongoDB
    def save_data(data):
    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.guazi
    collection = db.esc
    esc = {
    '标题': data[0],
    '二手车价格': data[1],
    '新车指导价': data[2],
    '车主': data[3],
    '上牌时间': data[4],
    '表显里程': data[5],
    '上牌地': data[6],
    '排放标准': data[7],
    '变速箱': data[8],
    '排量': data[9],
    '过户次数': data[10],
    '看车地点': data[11],
    '年检到期': data[12],
    '交强险': data[13],
    '商业险到期': data[14]
    }
    collection.insert_one(esc)


    if __name__ == '__main__':
    for i in range(1, 51):
    url = 'https://www.guazi.com/www/buy/o%sc-1/' % i
    detail_urls = parse_index()
    for detail_url in detail_urls:
    car_url = 'https://www.guazi.com' + detail_url
    car_data = parse_detail(car_url)
    save_data(car_data)
    # 在3-10秒之间随机暂停
    time.sleep(random.randint(3, 10))
    time.sleep(random.randint(5, 60))
    print('所有数据爬取完毕!')

    【4x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-11-14
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: guazi.py
    # @Software: PyCharm
    # =============================================

    from lxml import etree
    import requests
    import pymongo
    import time
    import random
    import os

    # 必须要有 Cookie 和 User-Agent,且两者必须对应(用浏览器访问网站后控制台里面复制)
    headers = {
    'Cookie': 'uuid=06ce7520-ebd1-45bc-f41f-a95f2c9b2283; ganji_uuid=7044571161649671972745; lg=1; clueSourceCode=%2A%2300; user_city_id=-1; sessionid=fefbd4f8-0a06-4e8a-dc49-8856e1a02a07; Hm_lvt_936a6d5df3f3d309bda39e92da3dd52f=1573469368,1573541270,1573541964,1573715863; close_finance_popup=2019-11-14; cainfo=%7B%22ca_a%22%3A%22-%22%2C%22ca_b%22%3A%22-%22%2C%22ca_s%22%3A%22seo_baidu%22%2C%22ca_n%22%3A%22default%22%2C%22ca_medium%22%3A%22-%22%2C%22ca_term%22%3A%22-%22%2C%22ca_content%22%3A%22-%22%2C%22ca_campaign%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22scode%22%3A%22-%22%2C%22keyword%22%3A%22-%22%2C%22ca_keywordid%22%3A%22-%22%2C%22display_finance_flag%22%3A%22-%22%2C%22platform%22%3A%221%22%2C%22version%22%3A1%2C%22client_ab%22%3A%22-%22%2C%22guid%22%3A%2206ce7520-ebd1-45bc-f41f-a95f2c9b2283%22%2C%22ca_city%22%3A%22wh%22%2C%22sessionid%22%3A%22fefbd4f8-0a06-4e8a-dc49-8856e1a02a07%22%7D; _gl_tracker=%7B%22ca_source%22%3A%22-%22%2C%22ca_name%22%3A%22-%22%2C%22ca_kw%22%3A%22-%22%2C%22ca_id%22%3A%22-%22%2C%22ca_s%22%3A%22self%22%2C%22ca_n%22%3A%22-%22%2C%22ca_i%22%3A%22-%22%2C%22sid%22%3A56473912809%7D; cityDomain=www; preTime=%7B%22last%22%3A1573720945%2C%22this%22%3A1573469364%2C%22pre%22%3A1573469364%7D; Hm_lpvt_936a6d5df3f3d309bda39e92da3dd52f=1573720946; rfnl=https://www.guazi.com/www/chevrolet/i2c-1r18/; antipas=675i0t513a7447M2L9y418Qq869',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36'
    }


    # 获取所有二手车详情页URL
    def parse_index():
    response = requests.get(url=url, headers=headers)
    tree = etree.HTML(response.text)
    url_list = tree.xpath('//li/a[@class="car-a"]/@href')
    # print(len(url_list))
    return url_list


    # 获取二手车详细信息
    def parse_detail(content):
    detail_response = requests.get(url=content, headers=headers)
    tree = etree.HTML(detail_response.text)

    # 标题
    title = tree.xpath('//h2[@class="titlebox"]/text()')
    # 移除字符串头尾空格
    title = [t.strip() for t in title]
    # 匹配到两个元素,只取其中一个为标题
    title = title[:1]
    # print(title)

    # 价格
    price_old = tree.xpath('//span[@class="pricestype"]/text()')
    # 移除字符串头尾空格
    price_old = [p.strip() for p in price_old]
    # 加入单位
    price_old = [''.join(price_old + ['万'])]
    # print(price_old)

    # 新车指导价
    price_new = tree.xpath('//span[@class="newcarprice"]/text()')
    # 移除字符串头尾空格
    price_new = [p.strip() for p in price_new]
    # 对字符串进行切片,只取数字多少万
    price_new = ['¥' + price_new[0].split('价')[1]]
    # print(price_new)

    # 车主
    owner = tree.xpath('//dl/dt/span/text()')
    owner = [owner[0].replace('车主:', '')]
    # print(owner)

    # 上牌时间
    spsj = tree.xpath('//li[@class="one"]/div/text()')
    # print(spsj)

    # 表显里程
    bxlc = tree.xpath('//li[@class="two"]/div/text()')
    # print(bxlc)

    # 上牌地
    spd = tree.xpath('//li[@class="three"]/div/text()')
    # 某些二手车没有上牌地,没有的将其赋值为:未知
    if len(spd) == 0:
    spd = ['未知']
    # print(spd)

    # 排放标准
    pfbz = tree.xpath('//li[@class="four"]/div/text()')
    pfbz = pfbz[:1]
    # print(pfbz)

    # 变速箱
    bsx = tree.xpath('//li[@class="five"]/div/text()')
    # print(bsx)

    # 排量
    pl = tree.xpath('//li[@class="six"]/div/text()')
    # print(pl)

    # 过户次数
    ghcs = tree.xpath('//li[@class="seven"]/div/text()')
    ghcs = [g.strip() for g in ghcs]
    ghcs = ghcs[:1]
    # print(ghcs)

    # 看车地点
    kcdd = tree.xpath('//li[@class="eight"]/div/text()')
    # print(kcdd)

    # 年检到期
    njdq = tree.xpath('//li[@class="nine"]/div/text()')
    # print(njdq)

    # 交强险
    jqx = tree.xpath('//li[@class="ten"]/div/text()')
    # print(jqx)

    # 商业险到期
    syxdq = tree.xpath('//li[@class="last"]/div/text()')
    syxdq = [s.strip() for s in syxdq]
    syxdq = syxdq[:1]
    # print(syxdq)

    # 保存车辆图片
    # 获取图片地址
    pic_url = tree.xpath('//li[@class="js-bigpic"]/img/@data-src')[0]
    pic_response = requests.get(pic_url)
    # 定义图片名称以及保存的文件夹
    pic_name = title[0] + '.jpg'
    dir_name = 'guazi_pic'
    # 如果没有该文件夹则创建该文件夹
    if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    # 定义储存路径
    pic_path = dir_name + '/' + pic_name
    with open(pic_path, "wb")as f:
    f.write(pic_response.content)

    # 将每辆二手车的所有信息合并为一个列表
    data = title + price_old + price_new + owner + spsj + bxlc + spd + pfbz + bsx + pl + ghcs + kcdd + njdq + jqx + syxdq
    return data


    # 将数据储存到 MongoDB
    def save_data(data):
    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.guazi
    collection = db.esc
    esc = {
    '标题': data[0],
    '二手车价格': data[1],
    '新车指导价': data[2],
    '车主': data[3],
    '上牌时间': data[4],
    '表显里程': data[5],
    '上牌地': data[6],
    '排放标准': data[7],
    '变速箱': data[8],
    '排量': data[9],
    '过户次数': data[10],
    '看车地点': data[11],
    '年检到期': data[12],
    '交强险': data[13],
    '商业险到期': data[14]
    }
    collection.insert_one(esc)


    if __name__ == '__main__':
    for i in range(1, 51):
    num = 0
    print('正在爬取第' + str(i) + '页数据...')
    url = 'https://www.guazi.com/www/buy/o%sc-1/' % i
    detail_urls = parse_index()
    for detail_url in detail_urls:
    car_url = 'https://www.guazi.com' + detail_url
    car_data = parse_detail(car_url)
    save_data(car_data)
    num += 1
    print('第' + str(num) + '条数据爬取完毕!')
    # 在3-10秒之间随机暂停
    time.sleep(random.randint(3, 10))
    print('第' + str(i) + '页数据爬取完毕!')
    print('=====================')
    time.sleep(random.randint(5, 60))
    print('所有数据爬取完毕!')

    【5x00】数据截图

    爬取的汽车图片:


    02

    储存到 MongoDB 的数据:


    03

    数据导出为 CSV 文件:


    04

    【6x00】程序不足的地方

    Cookie 过一段时间就会失效,数据还没爬取完就失效了,导致无法继续爬取;爬取效率不高,可以考虑多线程爬取

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 瓜子二手车 - - - -
    - - - - - Python3 爬虫实战 — 58同城武汉出租房【加密字体对抗】 - - /2019/10/21/A58-pyspider-58tongcheng/ - -

    爬取时间:2019-10-21
    爬取难度:★★★☆☆☆
    请求链接:https://wh.58.com/chuzu/
    爬取目标:58同城武汉出租房的所有信息
    涉及知识:网站加密字体的攻克、请求库 requests、解析库 Beautiful Soup、数据库 MySQL 的操作
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/58tongcheng
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】加密字体攻克思路

    F12 打开调试模板,通过页面分析,可以观察到,网站里面凡是涉及到有数字的地方,都是显示为乱码,这种情况就是字体加密了,那么是通过什么手段实现字体加密的呢?

    CSS 中有一个 @font-face 规则,它允许为网页指定在线字体,也就是说可以引入自定义字体,这个规则本意是用来消除对电脑字体的依赖,现在不少网站也利用这个规则来实现反爬

    右侧可以看到网站用的字体,其他的都是常见的微软雅黑,宋体等,但是有一个特殊的:fangchan-secret ,不难看出这应该就是58同城的自定义字体了


    01

    我们通过控制台看到的乱码事实上是由于 unicode 编码导致,查看网页源代码,我们才能看到他真正的编码信息


    02

    要攻克加密字体,那么我们肯定要分析他的字体文件了,先想办法得到他的加密字体文件,同样查看源代码,在源代码中搜索 fangchan-secret 的字体信息


    03

    选中的蓝色部分就是 base64 编码的加密字体字符串了,我们将其解码成二进制编码,写进 .woff 的字体文件,这个过程可以通过以下代码实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import requests
    import base64

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }

    url = 'https://wh.58.com/chuzu/'

    response = requests.get(url=url, headers=headers)
    # 匹配 base64 编码的加密字体字符串
    base64_string = response.text.split("base64,")[1].split("'")[0].strip()
    # 将 base64 编码的字体字符串解码成二进制编码
    bin_data = base64.decodebytes(base64_string.encode())
    # 保存为字体文件
    with open('58font.woff', 'wb') as f:
    f.write(bin_data)

    得到字体文件后,我们可以通过 FontCreator 这个软件来看看字体对应的编码是什么:


    04

    观察我们在网页源代码中看到的编码:类似于 &#x9fa4;&#x9f92;

    对比字体文件对应的编码:类似于 uni9FA4nui9F92

    可以看到除了前面三个字符不一样以外,后面的字符都是一样的,只不过英文大小写有所差异

    现在我们可能会想到,直接把编码替换成对应的数字不就OK了?然而并没有这么简单

    尝试刷新一下网页,可以观察到 base64 编码的加密字体字符串会改变,也就是说编码和数字并不是一一对应的,再次获取几个字体文件,通过对比就可以看出来


    05

    可以看到,虽然每次数字对应的编码都不一样,但是编码总是这10个,是不变的,那么编码与数字之间肯定存在某种对应关系,,我们可以将字体文件转换为 xml 文件来观察其中的对应关系,改进原来的代码即可实现转换功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import requests
    import base64
    from fontTools.ttLib import TTFont

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }

    url = 'https://wh.58.com/chuzu/'

    response = requests.get(url=url, headers=headers)
    # 匹配 base64 编码的加密字体字符串
    base64_string = response.text.split("base64,")[1].split("'")[0].strip()
    # 将 base64 编码的字体字符串解码成二进制编码
    bin_data = base64.decodebytes(base64_string.encode())
    # 保存为字体文件
    with open('58font.woff', 'wb') as f:
    f.write(bin_data)
    # 获取字体文件,将其转换为xml文件
    font = TTFont('58font.woff')
    font.saveXML('58font.xml')

    打开 58font.xml 文件并分析,在 <cmap> 标签内可以看到熟悉的类似于 0x94760x958f 的编码,其后四位字符恰好是网页字体的加密编码,可以看到每一个编码后面都对应了一个 glyph 开头的编码

    将其与 58font.woff 文件对比,可以看到 code 为 0x958f 这个编码对应的是数字 3,对应的 name 编码是 glyph00004


    06

    我们再次获取一个字体文件作为对比分析


    07

    依然是 0x958f 这个编码,两次对应的 name 分别是 glyph00004glyph00007,两次对应的数字分别是 36,那么结论就来了,每次发送请求,code 对应的 name 会随机发生变化,而 name 对应的数字不会发生变化,glyph00001 对应数字 0glyph00002 对应数字 1,以此类推

    那么以 glyph 开头的编码是如何对应相应的数字的呢?在 xml 文件里面,每个编码都有一个 TTGlyph 的标签,标签里面是一行一行的类似于 x,y 坐标的东西,这个其实就是用来绘制字体的,用 matplotlib 根据坐标画个图,就可以看到是一个数字


    08

    此时,我们就知道了编码与数字的对应关系,下一步,我们可以查找 xml 文件里,编码对应的 name 的值,也就是以 glyph 开头的编码,然后返回其对应的数字,再替换掉网页源代码里的编码,就能成功获取到我们需要的信息了!

    总结一下攻克加密字体的大致思路:

    • 分析网页,找到对应的加密字体文件

    • 如果引用的加密字体是一个 base64 编码的字符串,则需要转换成二进制并保存到 woff 字体文件中

    • 将字体文件转换成 xml 文件

    • 用 FontCreator 软件观察字体文件,结合 xml 文件,分析其编码与真实字体的关系

    • 搞清楚编码与字体的关系后,想办法将编码替换成正常字体


    【2x00】思维导图


    09

    【3x00】加密字体处理模块

    【3x01】获取字体文件并转换为xml文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def get_font(page_url, page_num):
    response = requests.get(url=page_url, headers=headers)
    # 匹配 base64 编码的加密字体字符串
    base64_string = response.text.split("base64,")[1].split("'")[0].strip()
    # print(base64_string)
    # 将 base64 编码的字体字符串解码成二进制编码
    bin_data = base64.decodebytes(base64_string.encode())
    # 保存为字体文件
    with open('58font.woff', 'wb') as f:
    f.write(bin_data)
    print('第' + str(page_num) + '次访问网页,字体文件保存成功!')
    # 获取字体文件,将其转换为xml文件
    font = TTFont('58font.woff')
    font.saveXML('58font.xml')
    print('已成功将字体文件转换为xml文件!')
    return response.text

    由主函数传入要发送请求的 url,利用字符串的 split() 方法,匹配 base64 编码的加密字体字符串,利用 base64 模块的 base64.decodebytes() 方法,将 base64 编码的字体字符串解码成二进制编码并保存为字体文件,利用 FontTools 库,将字体文件转换为 xml 文件


    【3x02】将加密字体编码与真实字体进行匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    def find_font():
    # 以glyph开头的编码对应的数字
    glyph_list = {
    'glyph00001': '0',
    'glyph00002': '1',
    'glyph00003': '2',
    'glyph00004': '3',
    'glyph00005': '4',
    'glyph00006': '5',
    'glyph00007': '6',
    'glyph00008': '7',
    'glyph00009': '8',
    'glyph00010': '9'
    }
    # 十个加密字体编码
    unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5']
    num_list = []
    # 利用xpath语法匹配xml文件内容
    font_data = etree.parse('./58font.xml')
    for unicode in unicode_list:
    # 依次循环查找xml文件里code对应的name
    result = font_data.xpath("//cmap//map[@code='{}']/@name".format(unicode))[0]
    # print(result)
    # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value
    for key in glyph_list.keys():
    if key == result:
    num_list.append(glyph_list[key])
    print('已成功找到编码所对应的数字!')
    # print(num_list)
    # 返回value列表
    return num_list

    由前面的分析,我们知道 name 的值(即以 glyph 开头的编码)对应的数字是固定的,glyph00001 对应数字 0glyph00002 对应数字 1,以此类推,所以可以将其构造成为一个字典 glyph_list

    同样将十个 code(即类似于 0x9476 的加密字体编码)构造成一个列表

    循环查找这十个 code 在 xml 文件里对应的 name 的值,然后将 name 的值与字典文件的 key 值进行对比,如果两者值相同,则获取这个 keyvalue 值,最终得到的列表 num_list,里面的元素就是 unicode_list 列表里面每个加密字体的真实值


    【3x03】替换掉网页中所有的加密字体编码

    1
    2
    3
    4
    5
    def replace_font(num, page_response):
    # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5
    result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9])
    print('已成功将所有加密字体替换!')
    return result

    传入由上一步 find_font() 函数得到的真实字体的列表,利用 replace() 方法,依次将十个加密字体编码替换掉


    【4x00】租房信息提取模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    def parse_pages(pages):
    num = 0
    soup = BeautifulSoup(pages, 'lxml')
    # 查找到包含所有租房的li标签
    all_house = soup.find_all('li', class_='house-cell')
    for house in all_house:
    # 标题
    title = house.find('a', class_='strongbox').text.strip()
    # print(title)

    # 价格
    price = house.find('div', class_='money').text.strip()
    # print(price)

    # 户型和面积
    layout = house.find('p', class_='room').text.replace(' ', '')
    # print(layout)

    # 楼盘和地址
    address = house.find('p', class_='infor').text.replace(' ', '').replace('\n', '')
    # print(address)

    # 如果存在经纪人
    if house.find('div', class_='jjr'):
    agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\n', '')
    # 如果存在品牌公寓
    elif house.find('p', class_='gongyu'):
    agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\n', '')
    # 如果存在个人房源
    else:
    agent = house.find('p', class_='geren').text.replace(' ', '').replace('\n', '')
    # print(agent)

    data = [title, price, layout, address, agent]
    save_to_mysql(data)
    num += 1
    print('第' + str(num) + '条数据爬取完毕,暂停3秒!')
    time.sleep(3)

    利用 BeautifulSoup 解析库很容易提取到相关信息,这里要注意的是,租房信息来源分为三种:经纪人、品牌公寓和个人房源,这三个的元素节点也不一样,因此匹配的时候要注意


    10

    【5x00】MySQL数据储存模块

    【5x01】创建MySQL数据库的表

    1
    2
    3
    4
    5
    6
    def create_mysql_table():
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders')
    cursor = db.cursor()
    sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)'
    cursor.execute(sql)
    db.close()

    首先指定数据库为 58tc_spiders,需要事先使用 MySQL 语句创建,也可以通过 MySQL Workbench 手动创建

    然后使用 SQL 语句创建 一个表:58tc_data,表中包含 title、price、layout、address、agent 五个字段,类型都为 varchar

    此创建表的操作也可以事先手动创建,手动创建后就不需要此函数了


    【5x02】将数据储存到MySQL数据库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def save_to_mysql(data):
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)'
    try:
    cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4]))
    db.commit()
    except:
    db.rollback()
    db.close()

    commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏


    【6x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-10-21
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: 58tongcheng.py
    # @Software: PyCharm
    # =============================================

    import requests
    import time
    import random
    import base64
    import pymysql
    from lxml import etree
    from bs4 import BeautifulSoup
    from fontTools.ttLib import TTFont

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }


    # 获取字体文件并转换为xml文件
    def get_font(page_url, page_num):
    response = requests.get(url=page_url, headers=headers)
    # 匹配 base64 编码的加密字体字符串
    base64_string = response.text.split("base64,")[1].split("'")[0].strip()
    # print(base64_string)
    # 将 base64 编码的字体字符串解码成二进制编码
    bin_data = base64.decodebytes(base64_string.encode())
    # 保存为字体文件
    with open('58font.woff', 'wb') as f:
    f.write(bin_data)
    print('第' + str(page_num) + '次访问网页,字体文件保存成功!')
    # 获取字体文件,将其转换为xml文件
    font = TTFont('58font.woff')
    font.saveXML('58font.xml')
    print('已成功将字体文件转换为xml文件!')
    return response.text


    # 将加密字体编码与真实字体进行匹配
    def find_font():
    # 以glyph开头的编码对应的数字
    glyph_list = {
    'glyph00001': '0',
    'glyph00002': '1',
    'glyph00003': '2',
    'glyph00004': '3',
    'glyph00005': '4',
    'glyph00006': '5',
    'glyph00007': '6',
    'glyph00008': '7',
    'glyph00009': '8',
    'glyph00010': '9'
    }
    # 十个加密字体编码
    unicode_list = ['0x9476', '0x958f', '0x993c', '0x9a4b', '0x9e3a', '0x9ea3', '0x9f64', '0x9f92', '0x9fa4', '0x9fa5']
    num_list = []
    # 利用xpath语法匹配xml文件内容
    font_data = etree.parse('./58font.xml')
    for unicode in unicode_list:
    # 依次循环查找xml文件里code对应的name
    result = font_data.xpath("//cmap//map[@code='{}']/@name".format(unicode))[0]
    # print(result)
    # 循环字典的key,如果code对应的name与字典的key相同,则得到key对应的value
    for key in glyph_list.keys():
    if key == result:
    num_list.append(glyph_list[key])
    print('已成功找到编码所对应的数字!')
    # print(num_list)
    # 返回value列表
    return num_list


    # 替换掉网页中所有的加密字体编码
    def replace_font(num, page_response):
    # 9476 958F 993C 9A4B 9E3A 9EA3 9F64 9F92 9FA4 9FA5
    result = page_response.replace('&#x9476;', num[0]).replace('&#x958f;', num[1]).replace('&#x993c;', num[2]).replace('&#x9a4b;', num[3]).replace('&#x9e3a;', num[4]).replace('&#x9ea3;', num[5]).replace('&#x9f64;', num[6]).replace('&#x9f92;', num[7]).replace('&#x9fa4;', num[8]).replace('&#x9fa5;', num[9])
    print('已成功将所有加密字体替换!')
    return result


    # 提取租房信息
    def parse_pages(pages):
    num = 0
    soup = BeautifulSoup(pages, 'lxml')
    # 查找到包含所有租房的li标签
    all_house = soup.find_all('li', class_='house-cell')
    for house in all_house:
    # 标题
    title = house.find('a', class_='strongbox').text.strip()
    # print(title)

    # 价格
    price = house.find('div', class_='money').text.strip()
    # print(price)

    # 户型和面积
    layout = house.find('p', class_='room').text.replace(' ', '')
    # print(layout)

    # 楼盘和地址
    address = house.find('p', class_='infor').text.replace(' ', '').replace('\n', '')
    # print(address)

    # 如果存在经纪人
    if house.find('div', class_='jjr'):
    agent = house.find('div', class_='jjr').text.replace(' ', '').replace('\n', '')
    # 如果存在品牌公寓
    elif house.find('p', class_='gongyu'):
    agent = house.find('p', class_='gongyu').text.replace(' ', '').replace('\n', '')
    # 如果存在个人房源
    else:
    agent = house.find('p', class_='geren').text.replace(' ', '').replace('\n', '')
    # print(agent)

    data = [title, price, layout, address, agent]
    save_to_mysql(data)
    num += 1
    print('第' + str(num) + '条数据爬取完毕,暂停3秒!')
    time.sleep(3)


    # 创建MySQL数据库的表:58tc_data
    def create_mysql_table():
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders')
    cursor = db.cursor()
    sql = 'CREATE TABLE IF NOT EXISTS 58tc_data (title VARCHAR(255) NOT NULL, price VARCHAR(255) NOT NULL, layout VARCHAR(255) NOT NULL, address VARCHAR(255) NOT NULL, agent VARCHAR(255) NOT NULL)'
    cursor.execute(sql)
    db.close()


    # 将数据储存到MySQL数据库
    def save_to_mysql(data):
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='58tc_spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO 58tc_data(title, price, layout, address, agent) values(%s, %s, %s, %s, %s)'
    try:
    cursor.execute(sql, (data[0], data[1], data[2], data[3], data[4]))
    db.commit()
    except:
    db.rollback()
    db.close()


    if __name__ == '__main__':
    create_mysql_table()
    print('MySQL表58tc_data创建成功!')
    for i in range(1, 71):
    url = 'https://wh.58.com/chuzu/pn' + str(i) + '/'
    response = get_font(url, i)
    num_list = find_font()
    pro_pages = replace_font(num_list, response)
    parse_pages(pro_pages)
    print('第' + str(i) + '页数据爬取完毕!')
    time.sleep(random.randint(3, 60))
    print('所有数据爬取完毕!')

    【7x00】数据截图


    11
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 58同城 - - - -
    - - - - - Python3 爬虫实战 — 模拟登陆12306【点触验证码对抗】 - - /2019/10/21/A57-pyspider-12306-login/ - -

    登陆时间:2019-10-21
    实现难度:★★★☆☆☆
    请求链接:https://kyfw.12306.cn/otn/resources/login.html
    实现目标:模拟登陆中国铁路12306,攻克点触验证码
    涉及知识:点触验证码的攻克、自动化测试工具 Selenium 的使用、对接在线打码平台
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/12306-login
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】思维导图


    01
    • 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证

    • 发送请求,出现验证码后,剪裁并保存验证码图片

    • 选择在线打码平台,获取其API,以字节流格式发送图片

    • 打码平台人工识别验证码,返回验证码的坐标信息

    • 解析返回的坐标信息,模拟点击验证码,完成验证后点击登陆


    02

    【2x00】打码平台选择

    关于打码平台:在线打码平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多,本次实战使用超级鹰打码平台

    使用打码平台:在超级鹰打码平台注册账号,官网:http://www.chaojiying.com/ ,充值一块钱得到 1000 题分,在用户中心里面申请一个软件 ID ,在价格体系里面确定验证码的类型,先观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1 至 4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 然后在开发文档里面获取其 Python API,下载下来以备后用


    【3x00】初始化模块

    【3x01】初始化函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    # 12306账号密码
    USERNAME = '155********'
    PASSWORD = '***********'

    # 超级鹰打码平台账号密码
    CHAOJIYING_USERNAME = '*******'
    CHAOJIYING_PASSWORD = '*******'

    # 超级鹰打码平台软件ID
    CHAOJIYING_SOFT_ID = '********'
    # 验证码类型
    CHAOJIYING_KIND = '9004'


    class CrackTouClick():
    def __init__(self):
    self.url = 'https://kyfw.12306.cn/otn/resources/login.html'
    # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    self.wait = WebDriverWait(self.browser, 20)
    self.username = USERNAME
    self.password = PASSWORD
    self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)

    定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录页面 url ,谷歌浏览器驱动的目录(path),浏览器启动参数等,将超级鹰账号密码等相关参数传递给超级鹰 API


    【3x02】账号密码输入函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def get_input_element(self):
    # 登录页面发送请求
    self.browser.get(self.url)
    # 登录页面默认是扫码登录,所以首先要点击账号登录
    login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account')))
    login.click()
    time.sleep(3)
    # 查找到账号密码输入位置的元素
    username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName')))
    password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password')))
    # 输入账号密码
    username.send_keys(self.username)
    password.send_keys(self.password)

    分析页面可知,登陆页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userNameJ-password,调用 send_keys() 方法输入账号密码


    【4x00】验证码处理模块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    def crack(self):
    # 调用账号密码输入函数
    self.get_input_element()
    # 调用验证码图片剪裁函数
    image = self.get_touclick_image()
    bytes_array = BytesIO()
    image.save(bytes_array, format='PNG')
    # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON
    result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND)
    print(result)
    # 调用验证码坐标解析函数
    locations = self.get_points(result)
    # 调用模拟点击验证码函数
    self.touch_click_words(locations)
    # 调用模拟点击登录函数
    self.login()
    try:
    # 查找是否出现用户的姓名,若出现表示登录成功
    success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生'))
    print(success)
    cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name')
    print('用户' + cc.text + '登录成功')
    # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值
    except TimeoutException:
    self.chaojiying.ReportError(result['pic_id'])
    self.crack()

    crack() 为验证码处理模块的主函数

    调用账号密码输入函数 get_input_element(),等待账号密码输入完毕

    调用验证码图片剪裁函数 get_touclick_image(),得到验证码图片

    利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON,如果识别成功,典型的返回结果类似于:

    1
    2
    {'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': 
    '1f8e1d4bef8b11484cb1f1f34299865b'}

    其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔

    调用 get_points() 函数解析超级鹰识别结果

    调用 touch_click_words() 函数对符合要求的图片进行点击

    调用模拟点击登录函数 login(),点击登陆按钮模拟登陆

    使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值


    【4x01】验证码图片剪裁函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    def get_touclick_image(self, name='12306.png'):
    # 获取验证码的位置
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    time.sleep(3)
    location = element.location
    size = element.size
    top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
    # 先对整个页面截图
    screenshot = self.browser.get_screenshot_as_png()
    screenshot = Image.open(BytesIO(screenshot))
    # 根据验证码坐标信息,剪裁出验证码图片
    captcha = screenshot.crop((left, top, right, bottom))
    captcha.save(name)
    return captcha

    首先查找到验证码的坐标信息,先对整个页面截图,然后根据验证码坐标信息,剪裁出验证码图片

    location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息


    【4x02】验证码坐标解析函数

    1
    2
    3
    4
    5
    6
    def get_points(self, captcha_result):
    # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔
    groups = captcha_result.get('pic_str').split('|')
    # 将坐标信息变成列表的形式
    locations = [[int(number) for number in group.split(',')] for group in groups]
    return locations

    get_points() 方法将超级鹰的验证码识别结果变成列表的形式


    【4x03】模拟点击验证码函数

    1
    2
    3
    4
    5
    6
    def touch_click_words(self, locations):
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    # 循环点击正确验证码的坐标
    for location in locations:
    print(location)
    ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform()

    循环提取正确的验证码坐标信息,依次点击验证码


    【5x00】登录模块

    1
    2
    3
    def login(self):
    submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login')))
    submit.click()

    分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录


    【6x00】完整代码

    【6x01】12306.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-10-21
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: 12306.py
    # @Software: PyCharm
    # =============================================

    import time
    from io import BytesIO
    from PIL import Image
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from chaojiying import ChaojiyingClient
    from selenium.common.exceptions import TimeoutException

    # 12306账号密码
    USERNAME = '155********'
    PASSWORD = '***********'

    # 超级鹰打码平台账号密码
    CHAOJIYING_USERNAME = '********'
    CHAOJIYING_PASSWORD = '********'

    # 超级鹰打码平台软件ID
    CHAOJIYING_SOFT_ID = '******'
    # 验证码类型
    CHAOJIYING_KIND = '9004'


    class CrackTouClick():
    def __init__(self):
    self.url = 'https://kyfw.12306.cn/otn/resources/login.html'
    # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    self.wait = WebDriverWait(self.browser, 20)
    self.username = USERNAME
    self.password = PASSWORD
    self.chaojiying = ChaojiyingClient(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)

    def crack(self):
    # 调用账号密码输入函数
    self.get_input_element()
    # 调用验证码图片剪裁函数
    image = self.get_touclick_image()
    bytes_array = BytesIO()
    image.save(bytes_array, format='PNG')
    # 利用超级鹰打码平台的 API PostPic() 方法把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个JSON
    result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND)
    print(result)
    # 调用验证码坐标解析函数
    locations = self.get_points(result)
    # 调用模拟点击验证码函数
    self.touch_click_words(locations)
    # 调用模拟点击登录函数
    self.login()
    try:
    # 查找是否出现用户的姓名,若出现表示登录成功
    success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭先生'))
    print(success)
    cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name')
    print('用户' + cc.text + '登录成功')
    # 若没有出现表示登录失败,继续重试,超级鹰会返回本次识别的分值
    except TimeoutException:
    self.chaojiying.ReportError(result['pic_id'])
    self.crack()

    # 账号密码输入函数
    def get_input_element(self):
    # 登录页面发送请求
    self.browser.get(self.url)
    # 登录页面默认是扫码登录,所以首先要点击账号登录
    login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account')))
    login.click()
    time.sleep(3)
    # 查找到账号密码输入位置的元素
    username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName')))
    password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password')))
    # 输入账号密码
    username.send_keys(self.username)
    password.send_keys(self.password)

    # 验证码图片剪裁函数
    def get_touclick_image(self, name='12306.png'):
    # 获取验证码的位置
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    time.sleep(3)
    location = element.location
    size = element.size
    top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size[
    'width']
    # 先对整个页面截图
    screenshot = self.browser.get_screenshot_as_png()
    screenshot = Image.open(BytesIO(screenshot))
    # 根据验证码坐标信息,剪裁出验证码图片
    captcha = screenshot.crop((left, top, right, bottom))
    captcha.save(name)
    return captcha

    # 验证码坐标解析函数,分析超级鹰返回的坐标
    def get_points(self, captcha_result):
    # 超级鹰识别结果以字符串形式返回,每个坐标都以|分隔
    groups = captcha_result.get('pic_str').split('|')
    # 将坐标信息变成列表的形式
    locations = [[int(number) for number in group.split(',')] for group in groups]
    return locations

    # 模拟点击验证码函数
    def touch_click_words(self, locations):
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    # 循环点击正确验证码的坐标
    for location in locations:
    print(location)
    ActionChains(self.browser).move_to_element_with_offset(element, location[0], location[1]).click().perform()

    # 模拟点击登录函数
    def login(self):
    submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login')))
    submit.click()


    if __name__ == '__main__':
    crack = CrackTouClick()
    crack.crack()

    【6x02】chaojiying.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    import requests
    from hashlib import md5


    class ChaojiyingClient(object):
    def __init__(self, username, password, soft_id):
    self.username = username
    password = password.encode('utf8')
    self.password = md5(password).hexdigest()
    self.soft_id = soft_id
    self.base_params = {
    'user': self.username,
    'pass2': self.password,
    'softid': self.soft_id,
    }
    self.headers = {
    'Connection': 'Keep-Alive',
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
    }

    def PostPic(self, im, codetype):
    """
    im: 图片字节
    codetype: 题目类型 参考 http://www.chaojiying.com/price.html
    """
    params = {
    'codetype': codetype,
    }
    params.update(self.base_params)
    files = {'userfile': ('ccc.jpg', im)}
    r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
    return r.json()

    def ReportError(self, im_id):
    """
    im_id:报错题目的图片ID
    """
    params = {
    'id': im_id,
    }
    params.update(self.base_params)
    r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
    return r.json()

    【7x00】效果实现动图

    最终实现效果图:(关键信息已经过打码处理)


    02
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 12306 - - - -
    - - - - - Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】 - - /2019/10/21/A56-pyspider-bilibili-login/ - -

    登陆时间:2019-10-21
    实现难度:★★★☆☆☆
    请求链接:https://passport.bilibili.com/login
    实现目标:模拟登陆哔哩哔哩,攻克滑动验证码
    涉及知识:滑动验证码的攻克、自动化测试工具 Selenium 的使用
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/bilibili-login
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】思维导图


    01
    • 利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证

    • 分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片

    • 对比原始的图片和带缺口的图片的像素,像素不同的地方就是缺口位置

    • 计算出滑块缺口的位置,得到所需要滑动的距离

    • 拖拽时要模仿人的行为,由于有个对准过程,所以要构造先快后慢的运动轨迹

    • 最后利用 Selenium 进行对滑块的拖拽


    02

    【2x00】登陆模块

    【2x01】初始化函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def init():
    global url, browser, username, password, wait
    url = 'https://passport.bilibili.com/login'
    # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    # 你的哔哩哔哩用户名
    username = '155********'
    # 你的哔哩哔哩登陆密码
    password = '***********'
    wait = WebDriverWait(browser, 20)

    global 关键字定义了发起请求的url、用户名、密码等全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时


    【2x02】登陆函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    def login():
    browser.get(url)
    # 获取用户名输入框
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    # 获取密码输入框
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    # 输入用户名
    user.send_keys(username)
    # 输入密码
    passwd.send_keys(password)
    # 获取登录按钮
    login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))
    # 随机暂停几秒
    time.sleep(random.random() * 3)
    # 点击登陆按钮
    login_btn.click()

    等待用户名输入框和密码输入框对应的 ID 节点加载出来

    获取这两个节点,用户名输入框 id="login-username",密码输入框 id="login-passwd"

    调用 send_keys() 方法输入用户名和密码

    获取登录按钮 class="btn btn-login"

    随机产生一个数并将其扩大三倍作为暂停时间

    最后调用 click() 方法实现登录按钮的点击


    【3x00】验证码处理模块

    【3x01】验证码元素查找函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    def find_element():
    # 获取带有缺口的图片
    c_background = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))
    # 获取需要滑动的图片
    c_slice = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))
    # 获取完整的图片
    c_full_bg = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))
    # 隐藏需要滑动的图片
    hide_element(c_slice)
    # 保存带有缺口的图片
    save_screenshot(c_background, 'back')
    # 显示需要滑动的图片
    show_element(c_slice)
    # 保存需要滑动的图片
    save_screenshot(c_slice, 'slice')
    # 显示完整的图片
    show_element(c_full_bg)
    # 保存完整的图片
    save_screenshot(c_full_bg, 'full')

    获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片

    分析页面代码,三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片

    定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute

    最后传值给 save_screenshot() 函数,进一步对验证码进行处理


    03

    【3x02】元素可见性设置函数

    1
    2
    3
    4
    5
    6
    7
    8
    # 设置元素不可见
    def hide_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")


    # 设置元素可见
    def show_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")

    【3x03】验证码截图函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    def save_screenshot(obj, name):
    try:
    # 首先对出现验证码后的整个页面进行截图保存
    pic_url = browser.save_screenshot('.\\bilibili.png')
    print("%s:截图成功!" % pic_url)
    # 计算传入的obj,也就是三张图片的位置信息
    left = obj.location['x']
    top = obj.location['y']
    right = left + obj.size['width']
    bottom = top + obj.size['height']
    # 打印输出一下每一张图的位置信息
    print('图:' + name)
    print('Left %s' % left)
    print('Top %s' % top)
    print('Right %s' % right)
    print('Bottom %s' % bottom)
    print('')
    # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存
    im = Image.open('.\\bilibili.png')
    im = im.crop((left, top, right, bottom))
    file_name = 'bili_' + name + '.png'
    im.save(file_name)
    except BaseException as msg:
    print("%s:截图失败!" % msg)

    location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增

    size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息

    首先调用 save_screenshot() 属性对整个页面截图并保存

    然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存


    【4x00】验证码滑动模块

    【4x01】滑动主函数

    1
    2
    3
    4
    5
    6
    def slide():
    distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))
    print('计算偏移量为:%s Px' % distance)
    trace = get_trace(distance - 5)
    move_to_gap(trace)
    time.sleep(3)

    get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证


    【4x02】缺口位置寻找函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def is_pixel_equal(bg_image, fullbg_image, x, y):
    # 获取两张图片对应像素点的RGB数据
    bg_pixel = bg_image.load()[x, y]
    fullbg_pixel = fullbg_image.load()[x, y]
    # 设定一个阈值
    threshold = 60
    # 比较两张图 RGB 的绝对值是否均小于定义的阈值
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(
    bg_pixel[2] - fullbg_pixel[2] < threshold)):
    return True
    else:
    return False

    将完整图片和缺口图片两个对象分别赋值给变量 bg_imagefullbg_image,接下来对比图片获取缺口。遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置


    【4x03】计算滑块移动距离函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def get_distance(bg_image, fullbg_image):
    # 滑块的初始位置
    distance = 60
    # 遍历两张图片的每个像素
    for i in range(distance, fullbg_image.size[0]):
    for j in range(fullbg_image.size[1]):
    # 调用缺口位置寻找函数
    if not is_pixel_equal(fullbg_image, bg_image, i, j):
    return i

    get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 缺口位置寻找函数判断两张图片同一位置的像素是否相同,若不相同则返回该点的值


    【4x04】构造移动轨迹函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    def get_trace(distance):
    trace = []
    # 设置加速距离为总距离的4/5
    faster_distance = distance * (4 / 5)
    # 设置初始位置、初始速度、时间间隔
    start, v0, t = 0, 0, 0.1
    while start < distance:
    if start < faster_distance:
    a = 10
    else:
    a = -10
    # 位移
    move = v0 * t + 1 / 2 * a * t * t
    # 当前时刻的速度
    v = v0 + a * t
    v0 = v
    start += move
    trace.append(round(move))
    # trace 记录了每个时间间隔移动了多少位移
    return trace

    get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系:

    1
    2
    move = v0 * t + 0.5 * a * t * t 
    v = v0 + a * t

    设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为10和-10,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了


    【4x05】模拟拖动函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def move_to_gap(trace):
    # 获取滑动按钮
    slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))
    # 点击并拖动滑块
    ActionChains(browser).click_and_hold(slider).perform()
    # 遍历运动轨迹获取每小段位移距离
    for x in trace:
    # 移动此位移
    ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.5)
    # 释放鼠标
    ActionChains(browser).release().perform()

    传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可


    【5x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-10-21
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: bilibili.py
    # @Software: PyCharm
    # =============================================

    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    from selenium.webdriver import ActionChains
    import time
    import random
    from PIL import Image


    # 初始化函数
    def init():
    global url, browser, username, password, wait
    url = 'https://passport.bilibili.com/login'
    # path是谷歌浏览器驱动的目录,如果已经将目录添加到系统变量,则不用设置此路径
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    # 你的哔哩哔哩用户名
    username = '155********'
    # 你的哔哩哔哩登录密码
    password = '***********'
    wait = WebDriverWait(browser, 20)


    # 登录函数
    def login():
    browser.get(url)
    # 获取用户名输入框
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    # 获取密码输入框
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    # 输入用户名
    user.send_keys(username)
    # 输入密码
    passwd.send_keys(password)
    # 获取登录按钮
    login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))
    # 随机暂停几秒
    time.sleep(random.random() * 3)
    # 点击登陆按钮
    login_btn.click()


    # 验证码元素查找函数
    def find_element():
    # 获取带有缺口的图片
    c_background = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))
    # 获取需要滑动的图片
    c_slice = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))
    # 获取完整的图片
    c_full_bg = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))
    # 隐藏需要滑动的图片
    hide_element(c_slice)
    # 保存带有缺口的图片
    save_screenshot(c_background, 'back')
    # 显示需要滑动的图片
    show_element(c_slice)
    # 保存需要滑动的图片
    save_screenshot(c_slice, 'slice')
    # 显示完整的图片
    show_element(c_full_bg)
    # 保存完整的图片
    save_screenshot(c_full_bg, 'full')


    # 设置元素不可见
    def hide_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")


    # 设置元素可见
    def show_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")


    # 验证码截图函数
    def save_screenshot(obj, name):
    try:
    # 首先对出现验证码后的整个页面进行截图保存
    pic_url = browser.save_screenshot('.\\bilibili.png')
    print("%s:截图成功!" % pic_url)
    # 计算传入的obj,也就是三张图片的位置信息
    left = obj.location['x']
    top = obj.location['y']
    right = left + obj.size['width']
    bottom = top + obj.size['height']
    # 打印输出一下每一张图的位置信息
    print('图:' + name)
    print('Left %s' % left)
    print('Top %s' % top)
    print('Right %s' % right)
    print('Bottom %s' % bottom)
    print('')
    # 在整个页面截图的基础上,根据位置信息,分别剪裁出三张验证码图片并保存
    im = Image.open('.\\bilibili.png')
    im = im.crop((left, top, right, bottom))
    file_name = 'bili_' + name + '.png'
    im.save(file_name)
    except BaseException as msg:
    print("%s:截图失败!" % msg)


    # 滑动模块的主函数
    def slide():
    distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))
    print('计算偏移量为:%s Px' % distance)
    trace = get_trace(distance - 5)
    move_to_gap(trace)
    time.sleep(3)


    # 计算滑块移动距离函数
    def get_distance(bg_image, fullbg_image):
    # 滑块的初始位置
    distance = 60
    # 遍历两张图片的每个像素
    for i in range(distance, fullbg_image.size[0]):
    for j in range(fullbg_image.size[1]):
    # 调用缺口位置寻找函数
    if not is_pixel_equal(fullbg_image, bg_image, i, j):
    return i


    # 缺口位置寻找函数
    def is_pixel_equal(bg_image, fullbg_image, x, y):
    # 获取两张图片对应像素点的RGB数据
    bg_pixel = bg_image.load()[x, y]
    fullbg_pixel = fullbg_image.load()[x, y]
    # 设定一个阈值
    threshold = 60
    # 比较两张图 RGB 的绝对值是否均小于定义的阈值
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(
    bg_pixel[2] - fullbg_pixel[2] < threshold)):
    return True
    else:
    return False


    # 构造移动轨迹函数
    def get_trace(distance):
    trace = []
    # 设置加速距离为总距离的4/5
    faster_distance = distance * (4 / 5)
    # 设置初始位置、初始速度、时间间隔
    start, v0, t = 0, 0, 0.1
    while start < distance:
    if start < faster_distance:
    a = 10
    else:
    a = -10
    # 位移
    move = v0 * t + 1 / 2 * a * t * t
    # 当前时刻的速度
    v = v0 + a * t
    v0 = v
    start += move
    trace.append(round(move))
    # trace 记录了每个时间间隔移动了多少位移
    return trace


    # 模拟拖动函数
    def move_to_gap(trace):
    # 获取滑动按钮
    slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))
    # 点击并拖动滑块
    ActionChains(browser).click_and_hold(slider).perform()
    # 遍历运动轨迹获取每小段位移距离
    for x in trace:
    # 移动此位移
    ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.5)
    # 释放鼠标
    ActionChains(browser).release().perform()


    if __name__ == '__main__':
    init()
    login()
    find_element()
    slide()

    【6x00】效果实现动图

    最终实现效果图:(关键信息已经过打码处理)


    04
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 哔哩哔哩 - - - -
    - - - - - Python3 爬虫实战 — 虎扑论坛步行街 - - /2019/10/12/A55-pyspider-hupu/ - -

    爬取时间:2019-10-12
    爬取难度:★★☆☆☆☆
    请求链接:https://bbs.hupu.com/bxj
    爬取目标:爬取虎扑论坛步行街的帖子,包含主题,作者,发布时间等,数据保存到 MongoDB 数据库
    涉及知识:请求库 requests、解析库 Beautiful Soup、数据库 MongoDB 的操作
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/hupu
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】循环爬取网页模块

    观察虎扑论坛步行街分区,请求地址为:https://bbs.hupu.com/bxj

    第一页:https://bbs.hupu.com/bxj

    第二页:https://bbs.hupu.com/bxj-2

    第三页:https://bbs.hupu.com/bxj-3

    不难发现,每增加一页,只需要添加 -页数 参数即可,最后一页是第 50 页,因此可以利用 for 循环依次爬取,定义一个 get_pages() 函数,返回初始化 Beautiful Soup 的对象 page_soup,方便后面的解析函数调用

    虽然一共有 50 页,但是当用户访问第 10 页以后的页面的时候,会要求登录虎扑,不然就没法查看,而且登录时会出现智能验证,所以程序只爬取前 10 页的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def get_pages(page_url):
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }
    response = requests.get(url=page_url, headers=headers)
    page_soup = BeautifulSoup(response.text, 'lxml')
    return page_soup

    if __name__ == '__main__':
    for i in range(1, 11):
    url = 'https://bbs.hupu.com/bxj-' + str(i)
    soup = get_pages(url)

    【2x00】解析模块

    使用 Beautiful Soup 对网页各个信息进行提取,最后将这些信息放进一个列表里,然后调用列表的 .append() 方法,再将每条帖子的列表依次加到另一个新列表里,最终返回的是类似于如下形式的列表:

    1
    [['帖子1', '作者1'], ['帖子2', '作者2'], ['帖子3', '作者3']]

    这样做的目的是:方便 MongoDB 依次储存每一条帖子的信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    def parse_pages(page_soup):
    data_list = []
    all_list = page_soup.find('ul', class_='for-list')
    post_list = all_list.find_all('li')
    # print(result_list)
    for post in post_list:
    # 帖子名称
    post_title = post.find('a', class_='truetit').text
    # print(post_title)
    # 帖子链接
    post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href']
    # print(post_url)
    # 作者
    author = post.select('.author > a')[0].text
    # print(author)
    # 作者主页
    author_url = post.select('.author > a')[0]['href']
    # print(author_url)
    # 发布日期
    post_date = post.select('.author > a')[1].text
    # print(post_date)
    reply_view = post.find('span', class_='ansour').text
    # 回复数
    post_reply = reply_view.split('/')[0].strip()
    # print(post_reply)
    # 浏览量
    post_view = reply_view.split('/')[1].strip()
    # print(post_view)
    # 最后回复时间
    last_data = post.select('.endreply > a')[0].text
    # print(last_data)
    # 最后回复用户
    last_user = post.select('.endreply > span')[0].text
    # print(last_user)

    data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user])

    # print(data_list)
    return data_list

    【3x00】MongoDB 数据储存模块

    首先使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port,指定数据库为 hupu,集合为 bxj

    将解析函数返回的列表传入到储存函数,依次循环该列表,对每一条帖子的信息进行提取并储存

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def mongodb(data_list):
    client = MongoClient('localhost', 27017)
    db = client.hupu
    collection = db.bxj
    for data in data_list:
    bxj = {
    '帖子名称': data[0],
    '帖子链接': data[1],
    '作者': data[2],
    '作者主页': data[3],
    '发布日期': str(data[4]),
    '回复数': data[5],
    '浏览量': data[6],
    '最后回复时间': str(data[7]),
    '最后回复用户': data[8]
    }
    collection.insert_one(bxj)

    【4x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-10-12
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: hupu.py
    # @Software: PyCharm
    # =============================================

    import requests
    import time
    import random
    from pymongo import MongoClient
    from bs4 import BeautifulSoup


    def get_pages(page_url):
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }
    response = requests.get(url=page_url, headers=headers)
    page_soup = BeautifulSoup(response.text, 'lxml')
    return page_soup


    def parse_pages(page_soup):
    data_list = []
    all_list = page_soup.find('ul', class_='for-list')
    post_list = all_list.find_all('li')
    # print(result_list)
    for post in post_list:
    # 帖子名称
    post_title = post.find('a', class_='truetit').text
    # print(post_title)
    # 帖子链接
    post_url = 'https://bbs.hupu.com' + post.find('a', class_='truetit')['href']
    # print(post_url)
    # 作者
    author = post.select('.author > a')[0].text
    # print(author)
    # 作者主页
    author_url = post.select('.author > a')[0]['href']
    # print(author_url)
    # 发布日期
    post_date = post.select('.author > a')[1].text
    # print(post_date)
    reply_view = post.find('span', class_='ansour').text
    # 回复数
    post_reply = reply_view.split('/')[0].strip()
    # print(post_reply)
    # 浏览量
    post_view = reply_view.split('/')[1].strip()
    # print(post_view)
    # 最后回复时间
    last_data = post.select('.endreply > a')[0].text
    # print(last_data)
    # 最后回复用户
    last_user = post.select('.endreply > span')[0].text
    # print(last_user)

    data_list.append([post_title, post_url, author, author_url, post_date, post_reply, post_view, last_data, last_user])

    # print(data_list)
    return data_list


    def mongodb(data_list):
    client = MongoClient('localhost', 27017)
    db = client.hupu
    collection = db.bxj
    for data in data_list:
    bxj = {
    '帖子名称': data[0],
    '帖子链接': data[1],
    '作者': data[2],
    '作者主页': data[3],
    '发布日期': str(data[4]),
    '回复数': data[5],
    '浏览量': data[6],
    '最后回复时间': str(data[7]),
    '最后回复用户': data[8]
    }
    collection.insert_one(bxj)


    if __name__ == '__main__':
    for i in range(1, 11):
    url = 'https://bbs.hupu.com/bxj-' + str(i)
    soup = get_pages(url)
    result_list = parse_pages(soup)
    mongodb(result_list)
    print('第', i, '页数据爬取完毕!')
    time.sleep(random.randint(3, 10))
    print('前10页所有数据爬取完毕!')

    【5x00】数据截图

    一共爬取到 1180 条数据:


    01

    【6x00】程序不足的地方

    程序只能爬取前 10 页的数据,因为虎扑论坛要求从第 11 页开始,必须登录账号才能查看,并且登录时会有智能验证,可以使用自动化测试工具 Selenium 模拟登录账号后再进行爬取。

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 虎扑论坛 - - - -
    - - - - - Python3 爬虫实战 — 安居客武汉二手房 - - /2019/10/09/A54-pyspider-anjuke/ - -

    爬取时间:2019-10-09
    爬取难度:★★☆☆☆☆
    请求链接:https://wuhan.anjuke.com/sale/
    爬取目标:爬取武汉二手房每一条售房信息,包含地理位置、价格、面积等,保存为 CSV 文件
    涉及知识:请求库 requests、解析库 Beautiful Soup、CSV 文件储存、列表操作、分页判断
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/anjuke
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】页面整体分析

    分析 安居客武汉二手房页面,这次爬取实战准备使用 BeautifulSoup 解析库,熟练 BeautifulSoup 解析库的用法,注意到该页面与其他页面不同的是,不能一次性看到到底有多少页,以前知道一共有多少页,直接一个循环爬取就行了,虽然可以通过改变 url 来尝试找到最后一页,但是这样就显得不程序员了😂,因此可以通过 BeautifulSoup 解析 下一页按钮,提取到下一页的 url,直到没有 下一页按钮 这个元素为止,从而实现所有页面的爬取,剩下的信息提取和储存就比较简单了


    【2x00】解析模块

    分析页面,可以发现每条二手房信息都是包含在 <li> 标签内的,因此可以使用 BeautifulSoup 解析页面得到所有的 <li> 标签,然后再循环访问每个 <li> 标签,依次解析得到每条二手房的各种信息


    01
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    def parse_pages(url, num):
    response = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(response.text, 'lxml')
    result_list = soup.find_all('li', class_='list-item')
    # print(len(result_list))
    for result in result_list:
    # 标题
    title = result.find('a', class_='houseListTitle').text.strip()
    # print(title)
    # 户型
    layout = result.select('.details-item > span')[0].text
    # print(layout)
    # 面积
    cover = result.select('.details-item > span')[1].text
    # print(cover)
    # 楼层
    floor = result.select('.details-item > span')[2].text
    # print(floor)
    # 建造年份
    year = result.select('.details-item > span')[3].text
    # print(year)
    # 单价
    unit_price = result.find('span', class_='unit-price').text.strip()
    # print(unit_price)
    # 总价
    total_price = result.find('span', class_='price-det').text.strip()
    # print(total_price)
    # 关键字
    keyword = result.find('div', class_='tags-bottom').text.strip()
    # print(keyword)
    # 地址
    address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\n', '')
    # print(address)
    # 详情页url
    details_url = result.find('a', class_='houseListTitle')['href']
    # print(details_url)

    if __name__ == '__main__':
    start_num = 0
    start_url = 'https://wuhan.anjuke.com/sale/'
    parse_pages(start_url, start_num)

    【3x00】循环爬取模块

    前面已经分析过,该网页是无法一下就能看到一共有多少页的,尝试找到最后一页,发现一共有50页,那么此时就可以搞个循环,一直到第50页就行了,但是如果有一天页面数增加了呢,那么代码的可维护性就不好了,我们可以观察 下一页按钮 ,当存在下一页的时候,是 <a> 标签,并且带有下一页的 URL,不存在下一页的时候是 <i> 标签,因此可以写个 if 语句,判断是否存在此 <a> 标签,若存在,表示有下一页,然后提取其 href 属性并传给解析模块,实现后面所有页面的信息提取,此外,由于安居客有反爬系统,我们还可以利用 Python中的 random.randint() 方法,在两个数值之间随机取一个数,传入 time.sleep() 方法,实现随机暂停爬取


    02
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 判断是否还有下一页
    next_url = soup.find_all('a', class_='aNxt')
    if len(next_url) != 0:
    num += 1
    print('第' + str(num) + '页数据爬取完毕!')
    # 3-60秒之间随机暂停
    time.sleep(random.randint(3, 60))
    parse_pages(next_url[0].attrs['href'], num)
    else:
    print('所有数据爬取完毕!')

    【4x00】数据储存模块

    数据储存比较简单,将每个二手房信息组成一个列表,依次写入到 anjuke.csv 文件中即可

    1
    2
    3
    4
    results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url]
    with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f:
    w = csv.writer(f)
    w.writerow(results)

    【5x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-10-09
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: anjuke.py
    # @Software: PyCharm
    # =============================================

    import requests
    import time
    import csv
    import random
    from bs4 import BeautifulSoup

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }


    def parse_pages(url, num):
    response = requests.get(url=url, headers=headers)
    soup = BeautifulSoup(response.text, 'lxml')
    result_list = soup.find_all('li', class_='list-item')
    # print(len(result_list))
    for result in result_list:
    # 标题
    title = result.find('a', class_='houseListTitle').text.strip()
    # print(title)
    # 户型
    layout = result.select('.details-item > span')[0].text
    # print(layout)
    # 面积
    cover = result.select('.details-item > span')[1].text
    # print(cover)
    # 楼层
    floor = result.select('.details-item > span')[2].text
    # print(floor)
    # 建造年份
    year = result.select('.details-item > span')[3].text
    # print(year)
    # 单价
    unit_price = result.find('span', class_='unit-price').text.strip()
    # print(unit_price)
    # 总价
    total_price = result.find('span', class_='price-det').text.strip()
    # print(total_price)
    # 关键字
    keyword = result.find('div', class_='tags-bottom').text.strip()
    # print(keyword)
    # 地址
    address = result.find('span', class_='comm-address').text.replace(' ', '').replace('\n', '')
    # print(address)
    # 详情页url
    details_url = result.find('a', class_='houseListTitle')['href']
    # print(details_url)
    results = [title, layout, cover, floor, year, unit_price, total_price, keyword, address, details_url]
    with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as f:
    w = csv.writer(f)
    w.writerow(results)

    # 判断是否还有下一页
    next_url = soup.find_all('a', class_='aNxt')
    if len(next_url) != 0:
    num += 1
    print('第' + str(num) + '页数据爬取完毕!')
    # 3-60秒之间随机暂停
    time.sleep(random.randint(3, 60))
    parse_pages(next_url[0].attrs['href'], num)
    else:
    print('所有数据爬取完毕!')


    if __name__ == '__main__':
    with open('anjuke.csv', 'a', newline='', encoding='utf-8-sig') as fp:
    writer = csv.writer(fp)
    writer.writerow(['标题', '户型', '面积', '楼层', '建造年份', '单价', '总价', '关键字', '地址', '详情页地址'])
    start_num = 0
    start_url = 'https://wuhan.anjuke.com/sale/'
    parse_pages(start_url, start_num)

    【6x00】数据截图


    03

    【7x00】程序不足的地方

    • 虽然使用了随机暂停爬取的方法,但是在爬取了大约 20 页的数据后依然会出现验证页面,导致程序终止

    • 原来设想的是可以由用户手动输入城市的拼音来查询不同城市的信息,方法是把用户输入的城市拼音和其他参数一起构造成一个 URL,然后对该 URL 发送请求,判断请求返回的代码,如果是 200 就代表可以访问,也就是用户输入的城市是正确的,然而发现即便是输入错误,该 URL 依然可以访问,只不过会跳转到一个正确的页面,没有搞清楚是什么原理,也就无法实现由用户输入城市来查询这个功能

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 安居客 - - - -
    - - - - - 使用 Hexo-Git-Backup 插件备份你的 Hexo 博客 - - /2019/09/29/A53-hexo-backup/ - -

    欢迎关注我的 CSDN 专栏:《个人博客搭建:Hexo+Github Pages》,从搭建到美化一条龙,帮你解决 Hexo 常见问题!


    由于 Hexo 博客是静态托管的,所有的原始数据都保存在本地,如果哪一天电脑坏了,或者是误删了本地数据,那就是叫天天不应叫地地不灵了,此时定时备份就显得比较重要了,常见的备份方法有:打包数据保存到U盘、云盘或者其他地方,但是早就有大神开发了备份插件:hexo-git-backup ,只需要一个命令就可以将所有数据包括主题文件备份到 github 了

    首先进入你博客目录,输入命令 hexo version 查看 Hexo 版本,如图所示,我的版本是 3.7.1:


    01

    安装备份插件,如果你的 Hexo 版本是 2.x.x,则使用以下命令安装:

    1
    $ npm install hexo-git-backup@0.0.91 --save

    如果你的 Hexo 版本是 3.x.x,则使用以下命令安装:

    1
    $ npm install hexo-git-backup --save

    到 Hexo 博客根目录的 _config.yml 配置文件里添加以下配置:

    1
    2
    3
    4
    5
    6
    7
    backup:
    type: git
    theme: material-x-1.2.1
    message: Back up my www.itrhx.com blog
    repository:
    github: git@github.com:TRHX/TRHX.github.io.git,backup
    coding: git@git.dev.tencent.com:TRHX/TRHX.git,backup

    参数解释:

    • theme:你要备份的主题名称
    • message:自定义提交信息
    • repository:仓库名,注意仓库地址后面要添加一个分支名,比如我就创建了一个 backup 分支

    最后使用以下命令备份你的博客:

    1
    $ hexo backup

    或者使用以下简写命令也可以:

    1
    $ hexo b

    备份成功后可以在你的仓库分支下看到备份的原始文件:


    02

    03
    ]]>
    - - - - - Hexo - - - - - - - Hexo - - 备份 - - - -
    - - - - - Python3 爬虫实战 — 豆瓣电影TOP250 - - /2019/09/28/A52-pyspider-doubantop250/ - -

    爬取时间:2019-09-27
    爬取难度:★★☆☆☆☆
    请求链接:https://movie.douban.com/top250 以及每部电影详情页
    爬取目标:爬取榜单上每一部电影详情页的数据,保存为 CSV 文件;下载所有电影海报到本地
    涉及知识:请求库 requests、解析库 lxml、Xpath 语法、正则表达式、CSV 和二进制数据储存、列表操作
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/douban-top250
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】循环爬取网页模块

    观察豆瓣电影 Top 250,请求地址为:https://movie.douban.com/top250

    每页展示25条电影信息,照例翻页观察 url 的变化:

    第一页:https://movie.douban.com/top250

    第二页:https://movie.douban.com/top250?start=25&filter=

    第三页:https://movie.douban.com/top250?start=50&filter=

    一共有10页,每次改变的是 start 的值,利用一个 for 循环,从 0 到 250 每隔 25 取一个值拼接到 url,实现循环爬取每一页,由于我们的目标是进入每一部电影的详情页,然后爬取详情页的内容,所以我们可以使用 Xpath 提取每一页每部电影详情页的 URL,将其赋值给 m_urls,并返回 m_urlsm_urls 是一个列表,列表元素就是电影详情页的 URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def index_pages(number):
    url = 'https://movie.douban.com/top250?start=%s&filter=' % number
    index_response = requests.get(url=url, headers=headers)
    tree = etree.HTML(index_response.text)
    m_urls = tree.xpath("//li/div/div/a/@href")
    return m_urls

    if __name__ == '__main__':
    for i in range(0, 250, 25):
    movie_urls = index_pages(i)

    【2x00】解析模块

    定义一个解析函数 parse_pages(),利用 for 循环,依次提取 index_pages() 函数返回的列表中的元素,也就是每部电影详情页的 URL,将其传给解析函数进行解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def index_pages(number):
    expressions

    def parse_pages(url):
    expressions

    if __name__ == '__main__':
    for i in range(0, 250, 25):
    movie_urls = index_pages(i)
    for movie_url in movie_urls:
    results = parse_pages(movie_url)

    详细看一下解析函数 parse_pages(),首先要对接收到的详情页 URL 发送请求,获取响应内容,然后再使用 Xpath 提取相关信息

    1
    2
    3
    def parse_pages(url):
    movie_pages = requests.get(url=url, headers=headers)
    parse_movie = etree.HTML(movie_pages.text)

    【2x01】Xpath 解析排名、电影名、评分信息

    其中排名、电影名和评分信息是最容易匹配到的,直接使用 Xpath 语法就可以轻松解决:

    1
    2
    3
    4
    5
    6
    7
    8
    # 排名
    ranking = parse_movie.xpath("//span[@class='top250-no']/text()")

    # 电影名
    name = parse_movie.xpath("//h1/span[1]/text()")

    # 评分
    score = parse_movie.xpath("//div[@class='rating_self clearfix']/strong/text()")

    【2x02】Xpath 解析参评人数

    接下来准备爬取有多少人参与了评价,分析一下页面:


    01

    如果只爬取这个 <span> 标签下的数字的话,没有任何提示信息,别人看了不知道是啥东西,所以把 人评价 这三个字也爬下来的话就比较好了,但是可以看到数字和文字不在同一个元素标签下,而且文字部分还有空格,要爬取的话就要把 class="rating_people"a 标签下所有的 text 提取出来,然后再去掉空格:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 参评人数
    # 匹配a节点
    value = parse_movie.xpath("//a[@class='rating_people']")
    # 提取a节点下所有文本
    string = [value[0].xpath('string(.)')]
    # 去除多余空格
    number = [a.strip() for a in string]

    # 此时 number = ['1617307人评价']

    这样做太麻烦了,我们可以直接提取数字,得到一个列表,然后使用另一个带有提示信息的列表,将两个列表的元素合并,组成一个新列表,这个新列表的元素就是提示信息+人数

    1
    2
    3
    4
    5
    6
    # 参评人数
    value = parse_movie.xpath("//span[@property='v:votes']/text()")
    # 合并元素
    number = [" ".join(['参评人数:'] + value)]

    # 此时 number = ['参评人数:1617307']


    【2x03】正则表达式解析制片国家、语言

    接下来尝试爬取制片国家/地区、语言等信息:


    02

    分析页面可以观察到,制片国家/地区和语言结构比较特殊,没有特别的 class 或者 id 属性,所包含的层次关系也太复杂,所以这里为了简便,直接采用正则表达式来匹配信息,就没有那么复杂了:

    1
    2
    3
    4
    5
    6
    7
    # 制片国家/地区
    value = re.findall('<span class="pl">制片国家/地区:</span>(.*?)<br/>', movie_pages.text)
    country = [" ".join(['制片国家:'] + value)]

    # 语言
    value = re.findall('<span class="pl">语言:</span>(.*?)<br/>', movie_pages.text)
    language = [" ".join(['语言:'] + value)]

    【3x00】返回解析数据

    其他剩下的信息皆可利用以上方法进行提取,所有信息提取完毕,最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表

    1
    return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)

    【4x00】数据储存模块

    定义一个数据保存函数 save_results()

    1
    2
    3
    4
    def save_results(data):
    with open('douban.csv', 'a', encoding="utf-8-sig") as fp:
    writer = csv.writer(fp)
    writer.writerow(data)

    注意:编码方式要设置为 utf-8-sig,如果设置为 utf-8,则文件会乱码,不设置编码,则可能会报一下类似错误:

    1
    UnicodeEncodeError: 'gbk' codec can't encode character '\ub3c4' in position 9: illegal multibyte sequence

    可以看到错误出现在 \ub3c4 上,将该 Unicode 编码转换为中文为 ,发现正是排名第 19 的电影:熔炉 도가니,因为标题有韩文,所以在储存为 CSV 文件时会报编码错误,而将编码设置为 utf-8-sig 就不会报错,具体原因参见:《Python 中文日文汉字乱码处理utf-8-sig》

    接下来是保存电影的海报到本地:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 保存电影海报
    poster = parse_movie.xpath("//div[@id='mainpic']/a/img/@src")
    response = requests.get(poster[0])
    name2 = re.sub(r'[A-Za-z\:\s]', '', name[0])
    poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg'
    dir_name = 'douban_poster'
    if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    poster_path = dir_name + '/' + poster_name
    with open(poster_path, "wb")as f:
    f.write(response.content)

    解析电影详情页,使用 Xpath 提取海报的 URL,向该 URL 发送请求

    图片以 排名+电影名.jpg 的方式命名,但是由于提取的电影名部分含有特殊字符,比如排名第 10 的电影:忠犬八公的故事 Hachi: A Dog’s Tale,其中有个冒号,而 Windows 文件命名是不能包含这些字符的,所以我们直接去除电影名包含的英文字符、空白字符、特殊字符,只留下中文,代码实现: name2 = re.sub(r'[A-Za-z\:\s]', '', name[0])

    定义一个文件夹名称 douban_poster,利用 os 模块判断当前是否存在该文件夹,若不存在就创建一个

    最后以二进制形式保存海报到当前目录的 douban_poster 文件夹下


    【5x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-09-27
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: douban.py
    # @Software: PyCharm
    # =============================================

    import requests
    from lxml import etree
    import csv
    import re
    import time
    import os

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}


    def index_pages(number):
    url = 'https://movie.douban.com/top250?start=%s&filter=' % number
    index_response = requests.get(url=url, headers=headers)
    tree = etree.HTML(index_response.text)
    m_urls = tree.xpath("//li/div/div/a/@href")
    return m_urls


    def parse_pages(url):
    movie_pages = requests.get(url=url, headers=headers)
    parse_movie = etree.HTML(movie_pages.text)

    # 排名
    ranking = parse_movie.xpath("//span[@class='top250-no']/text()")

    # 电影名
    name = parse_movie.xpath("//h1/span[1]/text()")

    # 评分
    score = parse_movie.xpath("//div[@class='rating_self clearfix']/strong/text()")

    # 参评人数
    value = parse_movie.xpath("//span[@property='v:votes']/text()")
    number = [" ".join(['参评人数:'] + value)]
    # value = parse_movie.xpath("//a[@class='rating_people']")
    # string = [value[0].xpath('string(.)')]
    # number = [a.strip() for a in string]
    # print(number)

    # 类型
    value = parse_movie.xpath("//span[@property='v:genre']/text()")
    types = [" ".join(['类型:'] + value)]

    # 制片国家/地区
    value = re.findall('<span class="pl">制片国家/地区:</span>(.*?)<br/>', movie_pages.text)
    country = [" ".join(['制片国家:'] + value)]

    # 语言
    value = re.findall('<span class="pl">语言:</span>(.*?)<br/>', movie_pages.text)
    language = [" ".join(['语言:'] + value)]

    # 上映时期
    value = parse_movie.xpath("//span[@property='v:initialReleaseDate']/text()")
    date = [" ".join(['上映日期:'] + value)]

    # 片长
    value = parse_movie.xpath("//span[@property='v:runtime']/text()")
    time = [" ".join(['片长:'] + value)]

    # 又名
    value = re.findall('<span class="pl">又名:</span>(.*?)<br/>', movie_pages.text)
    other_name = [" ".join(['又名:'] + value)]

    # 导演
    value = parse_movie.xpath("//div[@id='info']/span[1]/span[@class='attrs']/a/text()")
    director = [" ".join(['导演:'] + value)]

    # 编剧
    value = parse_movie.xpath("//div[@id='info']/span[2]/span[@class='attrs']/a/text()")
    screenwriter = [" ".join(['编剧:'] + value)]

    # 主演
    value = parse_movie.xpath("//div[@id='info']/span[3]")
    performer = [value[0].xpath('string(.)')]

    # URL
    m_url = ['豆瓣链接:' + movie_url]

    # IMDb链接
    value = parse_movie.xpath("//div[@id='info']/a/@href")
    imdb_url = [" ".join(['IMDb链接:'] + value)]

    # 保存电影海报
    poster = parse_movie.xpath("//div[@id='mainpic']/a/img/@src")
    response = requests.get(poster[0])
    name2 = re.sub(r'[A-Za-z\:\s]', '', name[0])
    poster_name = str(ranking[0]) + ' - ' + name2 + '.jpg'
    dir_name = 'douban_poster'
    if not os.path.exists(dir_name):
    os.mkdir(dir_name)
    poster_path = dir_name + '/' + poster_name
    with open(poster_path, "wb")as f:
    f.write(response.content)

    return zip(ranking, name, score, number, types, country, language, date, time, other_name, director, screenwriter, performer, m_url, imdb_url)


    def save_results(data):
    with open('douban.csv', 'a', encoding="utf-8-sig") as fp:
    writer = csv.writer(fp)
    writer.writerow(data)


    if __name__ == '__main__':
    num = 0
    for i in range(0, 250, 25):
    movie_urls = index_pages(i)
    for movie_url in movie_urls:
    results = parse_pages(movie_url)
    for result in results:
    num += 1
    save_results(result)
    print('第' + str(num) + '条电影信息保存完毕!')
    time.sleep(3)

    【6x00】数据截图


    03

    04

    【7x00】程序不足的地方

    程序不足的地方:豆瓣电影有反爬机制,当程序爬取到大约 150 条数据的时候,IP 就会被封掉,第二天 IP 才会解封,可以考虑综合使用多个代理、多个 User-Agent、随机时间暂停等方法进行爬取

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 豆瓣电影 - - - -
    - - - - - Python3 爬虫实战 — 猫眼电影TOP100 - - /2019/09/24/A51-pyspider-maoyantop100/ - -

    爬取时间:2019-09-23
    爬取难度:★☆☆☆☆☆
    请求链接:https://maoyan.com/board/4
    爬取目标:猫眼 TOP100 的电影名称、排名、主演、上映时间、评分、封面图地址,数据保存为 CSV 文件
    涉及知识:请求库 requests、解析库 lxml、Xpath 语法、CSV 文件储存
    完整代码:https://github.com/TRHX/Python3-Spider-Practice/tree/master/maoyan-top100
    其他爬虫实战代码合集(持续更新):https://github.com/TRHX/Python3-Spider-Practice
    爬虫实战专栏(持续更新):https://itrhx.blog.csdn.net/article/category/9351278


    【1x00】循环爬取网页模块

    观察猫眼电影TOP100榜,请求地址为:https://maoyan.com/board/4

    每页展示10条电影信息,翻页观察 url 变化:

    第一页:https://maoyan.com/board/4

    第二页:https://maoyan.com/board/4?offset=10

    第三页:https://maoyan.com/board/4?offset=20

    一共有10页,利用一个 for 循环,从 0 到 100 每隔 10 取一个值拼接到 url,实现循环爬取每一页

    1
    2
    3
    4
    5
    6
    7
    8
    def index_page(number):
    url = 'https://maoyan.com/board/4?offset=%s' % number
    response = requests.get(url=url, headers=headers)
    return response.text

    if __name__ == '__main__':
    for i in range(0, 100, 10):
    index = index_page(i)

    【2x00】解析模块

    定义一个页面解析函数 parse_page(),使用 lxml 解析库的 Xpath 方法依次提取电影排名(ranking)、电影名称(movie_name)、主演(performer)、上映时间(releasetime)、评分(score)、电影封面图 url(movie_img)

    通过对主演部分的提取发现有多余的空格符和换行符,循环 performer 列表,使用 strip() 方法去除字符串头尾空格和换行符

    电影评分分为整数部分和小数部分,依次提取两部分,循环遍历组成一个完整的评分

    最后使用 zip() 函数,将所有提取的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def parse_page(content):
    tree = etree.HTML(content)
    # 电影排名
    ranking = tree.xpath("//dd/i/text()")
    # 电影名称
    movie_name = tree.xpath('//p[@class="name"]/a/text()')
    # 主演
    performer = tree.xpath("//p[@class='star']/text()")
    performer = [p.strip() for p in performer]
    # 上映时间
    releasetime = tree.xpath('//p[@class="releasetime"]/text()')
    # 评分
    score1 = tree.xpath('//p[@class="score"]/i[@class="integer"]/text()')
    score2 = tree.xpath('//p[@class="score"]/i[@class="fraction"]/text()')
    score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))]
    # 电影封面图
    movie_img = tree.xpath('//img[@class="board-img"]/@data-src')
    return zip(ranking, movie_name, performer, releasetime, score, movie_img)

    【3x00】数据储存模块

    定义一个 save_results() 函数,将所有数据保存到 maoyan.csv 文件

    1
    2
    3
    4
    def save_results(result):
    with open('maoyan.csv', 'a') as fp:
    writer = csv.writer(fp)
    writer.writerow(result)

    【4x00】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    # =============================================
    # --*-- coding: utf-8 --*--
    # @Time : 2019-09-23
    # @Author : TRHX
    # @Blog : www.itrhx.com
    # @CSDN : https://blog.csdn.net/qq_36759224
    # @FileName: maoyan.py
    # @Software: PyCharm
    # =============================================

    import requests
    from lxml import etree
    import csv

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }


    def index_page(number):
    url = 'https://maoyan.com/board/4?offset=%s' % number
    response = requests.get(url=url, headers=headers)
    return response.text


    def parse_page(content):
    tree = etree.HTML(content)
    # 电影排名
    ranking = tree.xpath("//dd/i/text()")
    # 电影名称
    movie_name = tree.xpath('//p[@class="name"]/a/text()')
    # 主演
    performer = tree.xpath("//p[@class='star']/text()")
    performer = [p.strip() for p in performer]
    # 上映时间
    releasetime = tree.xpath('//p[@class="releasetime"]/text()')
    # 评分
    score1 = tree.xpath('//p[@class="score"]/i[@class="integer"]/text()')
    score2 = tree.xpath('//p[@class="score"]/i[@class="fraction"]/text()')
    score = [score1[i] + score2[i] for i in range(min(len(score1), len(score2)))]
    # 电影封面图
    movie_img = tree.xpath('//img[@class="board-img"]/@data-src')
    return zip(ranking, movie_name, performer, releasetime, score, movie_img)


    def save_results(result):
    with open('maoyan.csv', 'a') as fp:
    writer = csv.writer(fp)
    writer.writerow(result)


    if __name__ == '__main__':
    print('开始爬取数据...')
    for i in range(0, 100, 10):
    index = index_page(i)
    results = parse_page(index)
    for i in results:
    save_results(i)
    print('数据爬取完毕!')

    【4x00】数据截图


    01
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫实战 - - - - - - - 爬虫 - - 猫眼电影 - - - -
    - - - - - Python3 爬虫学习笔记 C18 - - /2019/09/21/A50-Python3-spider-C18/ - -
    Python3 爬虫学习笔记第十八章 —— 【爬虫框架 pyspider — 深入理解】

    【18.1】启动参数

    常用启动命令:pyspider all,完整命令结构为:pyspider [OPTIONS] COMMAND [ARGS],OPTIONS 为可选参数,包含以下参数:

    • -c, –config FILENAME:指定配置文件名称
    • –logging-config TEXT:日志配置文件名称,默认: pyspider/pyspider/logging.conf
    • –debug:开启调试模式
    • –queue-maxsize INTEGER:队列的最大长度
    • –taskdb TEXT:taskdb 的数据库连接字符串,默认: sqlite
    • –projectdb TEXT:projectdb 的数据库连接字符串,默认: sqlite
    • –resultdb TEXT:resultdb 的数据库连接字符串,默认: sqlite
    • –message-queue TEXT:消息队列连接字符串,默认: multiprocessing.Queue
    • –phantomjs-proxy TEXT:PhantomJS 使用的代理,ip:port 的形式
    • –data-path TEXT:数据库存放的路径
    • –add-sys-path / –not-add-sys-path:将当前工作目录添加到python lib搜索路径
    • –version:显示 pyspider 的版本信息
    • –help:显示帮助信息

    配置文件为一个 JSON 文件,一般为 config.json 文件,常用配置如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "taskdb": "mysql+taskdb://username:password@host:port/taskdb",
    "projectdb": "mysql+projectdb://username:password@host:port/projectdb",
    "resultdb": "mysql+resultdb://username:password@host:port/resultdb",
    "message_queue": "amqp://username:password@host:port/%2F",
    "webui": {
    "port": 5000,
    "username": "some_name",
    "password": "some_passwd",
    "need-auth": true
    }
    }

    可以设置对应的用户名,密码,端口等信息,使用命令 pyspider -c config.json all 即可运行


    【18.2】运行单个组件

    pyspider 的架构主要分为 Scheduler(调度器)、Fetcher(抓取器)、Processer(处理器)三个部分,都可以单独运行,基本命令: pyspider [component_name] [options]


    【18.2.1】运行 Scheduler

    1
    pyspider scheduler [OPTIONS]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Options:
    --xmlrpc /--no-xmlrpc
    --xmlrpc-host TEXT
    --xmlrpc-port INTEGER
    --inqueue-limit INTEGER 任务队列的最大长度,如果满了则新的任务会被忽略
    --delete-time INTEGER 设置为 delete 标记之前的删除时间
    --active-tasks INTEGER 当前活跃任务数量配置
    --loop-limit INTEGER 单轮最多调度的任务数量
    --fail-pause-num INTEGER 上次失败时自动暂停项目暂停次数,任务失败,将0设置为禁用
    --scheduler-cls TEXT Scheduler 使用的类
    --threads TEXT ThreadBaseScheduler 的线程号,默认值:4
    --help 显示帮助信息

    【18.2.2】运行 Fetcher

    1
    pyspider fetcher [OPTIONS]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Options:
    --xmlrpc /--no-xmlrpc
    --xmlrpc-host TEXT
    --xmlrpc-port INTEGER
    --poolsize INTEGER 同时请求的个数
    --proxy TEXT 使用的代理
    --user-agent TEXT 使用的 User-Agent
    --timeout TEXT 超时时间
    --phantomjs-endpoint TEXT phantomjs 的端点,通过 pyspider 启动 phantomjs
    --splash-endpoint TEXT 执行 splash 的端点:http://splash.readthedocs.io/en/stable/api.html execut
    --fetcher-cls TEXT Fetcher 使用的类
    --help 显示帮助信息

    【18.2.3】运行 Processer

    1
    pyspider processor [OPTIONS]
    1
    2
    3
    4
    Options:
    --processor-cls TEXT Processor 使用的类
    --process-time-limit INTEGER 脚本处理时间限制
    --help 显示帮助信息

    【18.2.4】运行 WebUI

    1
    pyspider webui [OPTIONS]
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Options:
    --host TEXT 运行地址
    --port INTEGER 运行端口
    --cdn TEXT JS 和 CSS 的 CDN 服务器
    --scheduler-rpc TEXT Scheduler 的 xmlrpc 路径
    --fetcher-rpc TEXT Fetcher 的 xmlrpc 路径
    --max-rate FLOAT 每个项目最大的 rate 值
    --max-burst FLOAT 每个项目最大的 burst 值
    --username TEXT Auth 验证的用户名
    --password TEXT Auth 验证的密码
    --need-auth 是否需要验证
    --webui-instance TEXT 运行时使用的 Flask 应用
    --process-time-limit INTEGER 调试中的脚本处理时间限制
    --help 显示帮助信息

    【18.3】crawl() 方法各参数

    参数文档:http://docs.pyspider.org/en/latest/apis/self.crawl/


    • url:爬取目标 URL,可以定义为单个 URL 字符串,也可以定义成 URL 列表

    • callback:回调函数,指定了该 URL 对应的响应内容用哪个方法来解析,示例:
    1
    2
    def on_start(self):
    self.crawl('http://www.itrhx.com/', callback=self.index_page)

    代码解释:指定 callbackindex_page,代表爬取 http://www.itrhx.com/ 得到的响应会用 index_page() 方法来解析,而 index_page() 方法的第一个参数就是响应对象,如下所示:

    1
    2
    def index_page(self, response):
    pass

    • age:任务的有效时间,如果某个任务在有效时间内且已经被执行,则它不会重复执行,有如下两种设置方法:
    1
    2
    def on_start(self):
    self.crawl('http://www.itrhx.com/', callback=self.callback, age=10*24*60*60)
    1
    2
    3
    @config(age=10 * 24 * 60 * 60)
    def callback(self):
    pass

    • priority:爬取任务的优先级,其值默认是 0,priority 的数值越大,对应的请求会越优先被调度,如下所示,2.html 页面将会优先爬取:
    1
    2
    3
    def index_page(self):
    self.crawl('http://www.itrhx.com/1.html', callback=self.index_page)
    self.crawl('http://www.itrhx.com/2.html', callback=self.detail_page, priority=1)

    • exetime:设置定时任务,其值是时间戳,默认是 0,即代表立即执行,如下所示表示该任务会在 30 分钟之后执行:
    1
    2
    3
    import time
    def on_start(self):
    self.crawl('http://www.itrhx.com/', callback=self.callback, exetime=time.time()+30*60)

    • retries:定义重试次数,其值默认是 3

    • itag:设置判定网页是否发生变化的节点值,在爬取时会判定次当前节点是否和上次爬取到的节点相同。如果节点相同,则证明页面没有更新,就不会重复爬取,如下所示:
    1
    2
    3
    def index_page(self, response):
    for item in response.doc('.item').items():
    self.crawl(item.find('a').attr.url, callback=self.detail_page, itag=item.find('.update-time').text())

    代码解释:设置 update-time 这个节点的值为 itag,在下次爬取时就会首先检测这个值有没有发生变化,如果没有变化,则不再重复爬取,否则执行爬取


    • auto_recrawl:开启时,爬取任务在过期后会重新执行,循环时间即定义的 age 时间长度,如下所示:
    1
    2
    def on_start(self):
    self.crawl('http://www.itrhx.com/', callback=self.callback, age=5*60*60, auto_recrawl=True)

    代码解释:定义 age 有效期为 5 小时,设置了 auto_recrawlTrue,这样任务就会每 5 小时执行一次


    • method:HTTP 请求方式,默认为 GET,如果想发起 POST 请求,可以将 method 设置为 POST

    • params:定义 GET 请求参数,如下所示表示两个等价的爬取任务:
    1
    2
    3
    def on_start(self):
    self.crawl('http://httpbin.org/get', callback=self.callback, params={'a': 123, 'b': 'c'})
    self.crawl('http://httpbin.org/get?a=123&b=c', callback=self.callback)

    • data:POST 表单数据,当请求方式为 POST 时,我们可以通过此参数传递表单数据,如下所示:
    1
    2
    def on_start(self):
    self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', data={'a': 123, 'b': 'c'})

    • files:上传的文件,需要指定文件名,如下所示:
    1
    2
    def on_start(self):
    self.crawl('http://httpbin.org/post', callback=self.callback, method='POST', files={field: {filename: 'content'}})

    • user_agent:爬取使用的 User-Agent

    • headers:爬取时使用的 Headers,即 Request Headers

    • cookies:爬取时使用的 Cookies,为字典格式

    • connect_timeout:在初始化连接时的最长等待时间,默认为 20 秒

    • timeout:抓取网页时的最长等待时间,默认为 120 秒

    • allow_redirects:确定是否自动处理重定向,默认为 True

    • validate_cert:确定是否验证证书,此选项对 HTTPS 请求有效,默认为 True

    • proxy:爬取时使用的代理,支持用户名密码的配置,格式为 username:password@hostname:port,如下所示:
    1
    2
    def on_start(self):
    self.crawl('http://httpbin.org/get', callback=self.callback, proxy='127.0.0.1:9743')

    也可以设置 craw_config 来实现全局配置,如下所示:

    1
    2
    class Handler(BaseHandler):
    crawl_config = {'proxy': '127.0.0.1:9743'}

    • fetch_type:开启 PhantomJS 渲染,如果遇到 JavaScript 渲染的页面,指定此字段即可实现 PhantomJS 的对接,pyspider 将会使用 PhantomJS 进行网页的抓取,如下所示:
    1
    2
    def on_start(self):
    self.crawl('https://www.taobao.com', callback=self.index_page, fetch_type='js')

    • js_script:页面加载完毕后执行的 JavaScript 脚本,如下所示,页面加载成功后将执行页面混动的 JavaScript 代码,页面会下拉到最底部:
    1
    2
    3
    4
    5
    6
    7
    def on_start(self):
    self.crawl('http://www.example.org/', callback=self.callback,
    fetch_type='js', js_script='''
    function() {window.scrollTo(0,document.body.scrollHeight);
    return 123;
    }
    ''')

    • js_run_at:代表 JavaScript 脚本运行的位置,是在页面节点开头还是结尾,默认是结尾,即 document-end

    • js_viewport_width/js_viewport_height:JavaScript 渲染页面时的窗口大小

    • load_images:在加载 JavaScript 页面时确定是否加载图片,默认为否

    • save:在不同的方法之间传递参数,如下所示:
    1
    2
    3
    4
    5
    6
    def on_start(self):
    self.crawl('http://www.example.org/', callback=self.callback,
    save={'page': 1})

    def callback(self, response):
    return response.save['page']

    • cancel:取消任务,如果一个任务是 ACTIVE 状态的,则需要将 force_update 设置为 True

    • force_update:即使任务处于 ACTIVE 状态,那也会强制更新状态

    【18.4】任务区分

    pyspider 判断两个任务是否是重复的是使用的是该任务对应的 URL 的 MD5 值作为任务的唯一 ID,如果 ID 相同,那么两个任务就会判定为相同,其中一个就不会爬取了

    某些情况下,请求的链接是同一个,但是 POST 的参数不同,这时可以重写 task_id() 方法,利用 URL 和 POST 的参数来生成 ID,改变这个 ID 的计算方式来实现不同任务的区分:

    1
    2
    3
    4
    import json
    from pyspider.libs.utils import md5string
    def get_taskid(self, task):
    return md5string(task['url']+json.dumps(task['fetch'].get('data', '')))

    【18.5】全局配置

    pyspider 可以使用 crawl_config 来指定全局的配置,配置中的参数会和 crawl() 方法创建任务时的参数合并:

    1
    2
    3
    4
    5
    class Handler(BaseHandler):
    crawl_config = {
    'headers': {'User-Agent': 'GoogleBot',}
    'proxy': '127.0.0.1:9743'
    }

    【18.6】定时爬取

    通过 every 属性来设置爬取的时间间隔,如下代码表示每天执行一次爬取:

    1
    2
    3
    4
    @every(minutes=24 * 60)
    def on_start(self):
    for url in urllist:
    self.crawl(url, callback=self.index_page)

    注意事项:如果设置了任务的有效时间(age 参数),因为在有效时间内爬取不会重复,所以要把有效时间设置得比重复时间更短,这样才可以实现定时爬取

    错误举例:设定任务的过期时间为 5 天,而自动爬取的时间间隔为 1 天,当第二次尝试重新爬取的时候,pyspider 会监测到此任务尚未过期,便不会执行爬取:

    1
    2
    3
    4
    5
    6
    7
    @every(minutes=24 * 60)
    def on_start(self):
    self.crawl('http://www.itrhx.com/', callback=self.index_page)

    @config(age=5 * 24 * 60 * 60)
    def index_page(self):
    pass
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - pyspider - - - -
    - - - - - Python3 爬虫学习笔记 C17 - - /2019/09/18/A49-Python3-spider-C17/ - -
    Python3 爬虫学习笔记第十七章 —— 【爬虫框架 pyspider — 基本使用】

    【17.1】初识 pyspider

    pyspider 是由国人 Binux 编写的一个 Python 爬虫框架

    pyspider 特性:

    • python 脚本控制,可以使用任何 html 解析包(内置 pyquery)
    • WEB 界面编写调试脚本,起停脚本,监控执行状态,查看活动历史,获取结果产出
    • 支持 MySQL、MongoDB、Redis、SQLite、Elasticsearch、PostgreSQL
    • 对接了 PhantomJS,支持抓取 JavaScript 的页面
    • 组件可替换,支持单机和分布式部署,支持 Docker 部署
    • 提供优先级控制、失败重试、定时抓取等功能

    Windows 系统安装 pyspider:

    使用命令 pip install pyspider 安装,若报 PyCurl 相关错误,可访问 https://www.lfd.uci.edu/~gohlke/pythonlibs/#pycurl 下载对应 wheel 文件并使用命令 pip install whl文件名 安装即可

    如果要爬取 JavaScrip 渲染的页面,还要下载 PhantomJS,并将 PhantomJS 的路径配置到环境变量里,或者直接复制到 Python 安装目录的 Scripts 文件夹,需要用到数据库储存的话,同样要安装好相应的数据库

    准备就绪后,使用 pyspider all 命令可启动 pyspider,浏览器打开:http://localhost:5000/ 可以看到 pyspider 的 WebUI 管理界面


    【17.2】使用 pyspider


    【17.2.1】主界面

    当成功创建了一个爬虫项目后,主界面如下所示:


    01
    • Recent Active Tasks:查看最近活动的任务,会跳转到一个页面有列表显示

    • Create:创建一个新的爬虫项目

    • group:定义项目的分组,以方便管理,若 group 设置为 delete,则该项目将会在24小时之后删除

    • project name:爬虫项目名称

    • status:项目状态,各状态如下:
      TODO:一个爬虫项目刚刚创建时的状态,此状态下可以编辑 Python 代码
      STOP:中止项目的运行
      CHECKING:当一个运行中的项目被编辑时项目状态会被自动设置成此状态并中止运行
      DEBUG:会运行爬虫,顾名思义找 BUG,一般来说用于调试阶段
      RUNNING:运行爬虫项目
      PAUSED:项目暂停运行,默认没有这个状态,但是当你在运行过程中突然断网就会出现此状态

    • rate/burst:当前的爬取速率,rate 代表 1 秒发出多少个请求,burst 相当于流量控制中的令牌桶算法的令牌数,rate 和 burst 设置的越大,爬取速率越快,速率的设定需要考虑本机性能和爬取过快被封的问题

    • avg time:任务平均时间

    • process:5m、1h、1d 分别指的是最近 5 分、1 小时、1 天内的请求情况,all 代表所有的请求情况,请求由不同颜色表示,蓝色的代表等待被执行的请求,绿色的代表成功的请求,黄色的代表请求失败后等待重试的请求,红色的代表失败次数过多而被忽略的请求

    • actions:对爬虫项目的操作,各操作如下:
      Run:立即执行任务,需要 status 为 RUNNING 或者 DEBUG 状态;假如在配置的调度执行时间内已经执行过,再点 run 是无效的,需要删除 task.db 里的数据才行
      Active Tasks:查看当前爬虫项目的活动任务
      Results:查看项目运行结果


    【17.2.2】项目界面

    创建一个爬虫项目,界面如下所示:


    02
    • 创建项目:点击 Create 即可新建一个爬虫项目
    • Project Name:爬虫项目名称
    • Start URL(s) :爬虫入口地址,选填,可在项目中更改

    项目创建完成进入调试界面:


    03
    • 调试界面右边:编写代码的区域

    • 调试界面左边:调试的区域,用于执行代码,显示输出信息等用途

    • run:单步调试爬虫程序,点击就可运行当前任务

    • < > 箭头:上一步、下一步,用于调试过程中切换到上一步骤或者下一步骤

    • save:保存当前代码,当代码变更后只有保存了再运行才能得到最新结果

    • enable css selector helper: CSS 选择器辅助程序

    • web:页面预览

    • html:可以查看页面源代码

    • follows:表示爬取请求,点击可查看所有的请求

    在新建一个爬虫项目的时候,pyspider 已经自动生成了如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    # Created on 2019-09-17 21:18:13
    # Project: 2

    from pyspider.libs.base_handler import *


    class Handler(BaseHandler):
    crawl_config = {
    }

    @every(minutes=24 * 60)
    def on_start(self):
    self.crawl('__START_URL__', callback=self.index_page)

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
    for each in response.doc('a[href^="http"]').items():
    self.crawl(each.attr.href, callback=self.detail_page)

    @config(priority=2)
    def detail_page(self, response):
    return {
    "url": response.url,
    "title": response.doc('title').text(),
    }
    • class Handler():pyspider 爬虫的主类,可以在此处定义爬取、解析、存储的逻辑。整个爬虫的功能只需要一个 Handler 即可完成

    • crawl_config 属性:项目的所有爬取配置将会统一定义到这里,如定义 headers、设置代理等,配置之后全局生效

    • on_start() 方法:爬取入口,初始的爬取请求会在这里产生,该方法通过调用 crawl() 方法即可新建一个爬取请求,第一个参数是爬取的 URL,另一个参数 callback 指定了这个页面爬取成功后用哪个方法进行解析,默认指定为 index_page() 方法,即如果这个 URL 对应的页面爬取成功了,那 Response 将交给 index_page() 方法解析

    • index_page() 方法:接收 Response 参数,Response 对接了 pyquery。直接调用 doc() 方法传入相应的 CSS 选择器,就可以像 pyquery 一样解析此页面,代码中默认是 a[href^="http"],即解析页面的所有链接,然后将链接遍历,再次调用了 crawl() 方法生成了新的爬取请求,同时再指定了 callback 为 detail_page,表示这些页面爬取成功了就调用 detail_page() 方法解析。index_page() 实现了两个功能,一是将爬取的结果进行解析,二是生成新的爬取请求

    • detail_page() 方法:同样接收 Response 作为参数。detail_page() 抓取的就是详情页的信息,就不会生成新的请求,只对 Response 对象做解析,解析之后将结果以字典的形式返回。当然也可以进行后续处理,如将结果保存到数据库等操作

    PS:pyspider 默认的 web 预览页面窗口较小,可以找到 pyspider 文件夹有个 debug.min.css 文件(如:E:\Python\Lib\site-packages\pyspider\webui\static\debug.min.css),搜索 iframe,将原样式:iframe{border-width:0;width:100%} 改为 iframe{border-width:0;width:100%;height:400px !important} 即可,清除浏览器缓存后就会生效!


    【17.3】使用 pyspider 爬取去哪儿网

    爬取地址:http://travel.qunar.com/travelbook/list.htm
    爬取目标:去哪儿网旅游攻略,发帖作者、标题、正文等


    【17.3.1】爬取首页

    创建一个名为 qunar 的爬虫项目,Start URL 设置为 http://travel.qunar.com/travelbook/list.htm ,点击 run 出现一个爬取请求


    04

    左边调试区域出现以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "process": {
    "callback": "on_start"
    },
    "project": "qunar",
    "taskid": "data:,on_start",
    "url": "data:,on_start"
    }

    callback 为 on_start,表示此时执行了 on_start() 方法。在 on_start() 方法中,利用 crawl() 方法即可生成一个爬取请求,点击 index_page 链接后面的箭头会出现许多新的爬取请求,即首页所包含的所有链接


    05

    此时左边调试区域代码变为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "fetch": {},
    "process": {
    "callback": "index_page"
    },
    "project": "qunar",
    "schedule": {
    "age": 864000
    },
    "taskid": "73a789f99528a2bdc3ab83a13902962a",
    "url": "http://travel.qunar.com/travelbook/list.htm"
    }

    callback 变为了 index_page,表示此时执行了 index_page() 方法。传入 index_page() 方法的 response 参数为刚才生成的第一个爬取请求的 response 对象,然后调用 doc() 方法,传入提取所有 a 节点的 CSS 选择器,获取 a 节点的属性 href,实现了页面所有链接的提取,随后遍历所有链接,调用 crawl() 方法,把每个链接构造成新的爬取请求,可以看到 follows 新生成了 229 个爬取请求。点击 web 按钮可以直接预览当前页面,点击 html 按钮可以查看此页面源代码


    【17.3.2】信息匹配

    代码 for each in response.doc('a[href^="http"]').items(): 实现了对整个页面链接的获取,我们需要提取网页的攻略的标题,内容等信息,那么直接替换 doc() 方法里的匹配语句即可,pyspider 提供了非常方便的 CSS 选择器,点击 enable css selector helper 按钮后,选择要匹配的信息并点击,再点击箭头 add to editor 即可得到匹配语句


    06

    完成了 CSS 选择器的替换,点击 save 保存,再次点击 run 重新执行 index_page() 方法,可以看到 follows 变为了 10 个,即抓取到了 10 篇攻略


    【17.3.3】抓取下一页数据

    每一页只有 10 篇攻略,想要爬取所有页面的攻略,必须要得到下一页的数据,优化 index_page() 方法:

    1
    2
    3
    4
    5
    6
    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
    for each in response.doc('li > .tit > a').items():
    self.crawl(each.attr.href, callback=self.detail_page)
    next = response.doc('.next').attr.href
    self.crawl(next, callback=self.index_page)

    匹配下一页按钮,获取下一页按钮的 URL 并赋值给 next,将该 URL 传给 crawl() 方法,指定回调函数为 index_page() 方法,这样会再次调用 index_page() 方法,提取下一页的攻略标题


    【17.3.4】抓取JS渲染数据

    随便点击一个获取到的攻略,预览该页面,可以观察到头图一直在加载中,切换到 html 查看源代码页面,可以观察到没有 img 节点,那么此处就是后期经过 JavaScript 渲染后才出现的


    07

    针对 JavaScript 渲染页面,可以通过 PhantomJS 来实现,具体到 pyspider 中,只需要在 index_page()crawl() 抓取方法中添加一个参数 fetch_type 即可:

    1
    2
    3
    4
    5
    6
    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
    for each in response.doc('li > .tit > a').items():
    self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
    next = response.doc('.next').attr.href
    self.crawl(next, callback=self.index_page)

    保存之后再次运行即可看到正常页面


    【17.3.5】抓取所有数据

    改写 detail_page() 方法,同样通过 CSS 选择器提取 URL、标题、日期、作者、正文、图片等信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @config(priority=2)
    def detail_page(self, response):
    return {
    'url': response.url,
    'title': response.doc('#booktitle').text(),
    'date': response.doc('.when .data').text(),
    'day': response.doc('.howlong .data').text(),
    'who': response.doc('.who .data').text(),
    'text': response.doc('#b_panel_schedule').text(),
    'image': response.doc('.cover_img').attr.src
    }

    【17.3.6】启动爬虫项目

    该爬虫项目完整代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #!/usr/bin/env python
    # -*- encoding: utf-8 -*-
    # Created on 2019-09-18 09:48:29
    # Project: qunar

    from pyspider.libs.base_handler import *


    class Handler(BaseHandler):
    crawl_config = {
    }

    @every(minutes=24 * 60)
    def on_start(self):
    self.crawl('http://travel.qunar.com/travelbook/list.htm', callback=self.index_page)

    @config(age=10 * 24 * 60 * 60)
    def index_page(self, response):
    for each in response.doc('li > .tit > a').items():
    self.crawl(each.attr.href, callback=self.detail_page, fetch_type='js')
    next = response.doc('.next').attr.href
    self.crawl(next, callback=self.index_page)

    @config(priority=2)
    def detail_page(self, response):
    return {
    'url': response.url,
    'title': response.doc('#booktitle').text(),
    'date': response.doc('.when .data').text(),
    'day': response.doc('.howlong .data').text(),
    'who': response.doc('.who .data').text(),
    'text': response.doc('#b_panel_schedule').text(),
    'image': response.doc('.cover_img').attr.src
    }

    保存代码后,回到主界面,将项目 status 修改为 RUNNING ,点击 actions 的 run 按钮即可启动爬虫


    08

    点击 Active Tasks,即可查看最近请求的详细状况:


    09

    点击 Results,即可查看所有的爬取结果:


    10

    另外,右上角还可以选择 JSON、CSV 格式

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - pyspider - - - -
    - - - - - Hexo 博客提交百度、谷歌搜索引擎收录 - - /2019/09/17/A48-submit-search-engine-inclusion/ - -

    ● 写在前面(必看)

    网站在没有提交搜索引擎收录之前,直接搜索你网站的内容是搜不到的,只有提交搜索引擎之后,搜索引擎才能收录你的站点,通过爬虫抓取你网站的东西,对于 hexo 博客来说,如果你是部署在 GitHub Pages,那么你是无法被百度收录的,因为 GitHub 禁止了百度爬虫,最常见的解决办法是双线部署到 Coding Pages 和 GitHub Pages,因为百度爬虫可以爬取到 Coding 上的内容,从而实现百度收录,如果你的 hexo 博客还没有实现双线部署,请参考:《Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS》,另外百度收录的所需的时间较长,大约半个月左右才会看到效果!


    ● 查看网站是否被收录

    首先我们可以输入 site:域名 来查看域名是否被搜索引擎收录,如下图所示,表示没有收录:


    01

    ● 百度资源平台添加网站

    访问百度搜索资源平台官网,注册或者登陆百度账号,依次选择【用户中心】-【站点管理】,添加你的网站,在添加站点时会让你选择协议头(http 或者 https),如果选择 https,它会验证你的站点,大约能在一天之内完成,我的网站已经实现了全站 https,因此选择了 https 协议,但是不知道为什么始终验证失败,实在是无解,只能选择 http 协议了,如果你的站点也实现了全站 https,也可以尝试一下


    02

    之后会让你验证网站所有权,提供三种验证方式:

    • 文件验证:下载给定的文件,将其放到本地主题目录 source 文件夹,然后部署上去完成验证
    • HTML 标签验证:一般是给一个 meta 标签,放到首页 <head></head> 标签之间即可完成验证
    • CNAME 验证:个人觉得这种方法最简单,去域名 DNS 添加一个 CNAME 记录即可完成验证

    03

    04

    ● 提交百度搜索

    百度提供了自动提交和手动提交两种方式,其中自动提交又分为主动推送、自动推送和 sitemap 三种方式,以下是官方给出的解释:

    • 主动推送:最为快速的提交方式,推荐您将站点当天新产出链接立即通过此方式推送给百度,以保证新链接可以及时被百度收录

    • 自动推送:是轻量级链接提交组件,将自动推送的 JS 代码放置在站点每一个页面源代码中,当页面被访问时,页面链接会自动推送给百度,有利于新页面更快被百度发现

    • sitemap:您可以定期将网站链接放到sitemap中,然后将sitemap提交给百度。百度会周期性的抓取检查您提交的sitemap,对其中的链接进行处理,但收录速度慢于主动推送

    • 手动提交:如果您不想通过程序提交,那么可以采用此种方式,手动将链接提交给百度

    四种提交方式对比:

    方式主动推送自动推送Sitemap手动提交
    速度最快——————
    开发成本不需开发
    可提交量
    是否建议提交历史连接
    和其他提交方法是否有冲突

    个人推荐同时使用主动推送和 sitemap 方式,下面将逐一介绍这四种提交方式的具体实现方法


    ● 主动推送

    在博客根目录安装插件 npm install hexo-baidu-url-submit --save,然后在根目录 _config.yml 文件里写入以下配置:

    1
    2
    3
    4
    5
    baidu_url_submit:
    count: 1 # 提交最新的多少个链接
    host: www.itrhx.com # 在百度站长平台中添加的域名
    token: your_token # 秘钥
    path: baidu_urls.txt # 文本文档的地址, 新链接会保存在此文本文档里

    其中的 token 可以在【链接提交】-【自动提交】-【主动推送】下面看到,接口调用地址最后面 token=xxxxx 即为你的 token


    05

    同样是在根目录的 _config.yml 文件,大约第 17 行处,url 要改为在百度站长平台添加的域名,也就是你网站的首页地址:

    1
    2
    3
    4
    # URL
    url: https://www.itrhx.com
    root: /
    permalink: :year/:month/:day/:title/

    最后,加入新的 deployer:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # Deployment
    ## Docs: https://hexo.io/docs/deployment.html
    deploy:
    - type: git
    repository:
    github: git@github.com:TRHX/TRHX.github.io.git # 这是原来的 github 配置
    coding: git@git.dev.tencent.com:TRHX/TRHX.git # 这是原来的 coding 配置
    branch: master
    - type: baidu_url_submitter # 这是新加的主动推送

    最后执行 hexo g -d 部署一遍即可实现主动推送,推送成功的标志是:在执行部署命令最后会显示类似如下代码:

    1
    2
    {"remain":4999953,"success":47}
    INFO Deploy done: baidu_url_submitter

    这表示有 47 个页面已经主动推送成功,remain 的意思是当天剩余的可推送 url 条数

    主动推送相关原理介绍:

    • 新链接的产生:hexo generate 会产生一个文本文件,里面包含最新的链接
    • 新链接的提交:hexo deploy 会从上述文件中读取链接,提交至百度搜索引擎

    该插件的 GitHub 地址:https://github.com/huiwang/hexo-baidu-url-submit


    ● 自动推送

    关于自动推送百度官网给出的解释是:自动推送是百度搜索资源平台为提高站点新增网页发现速度推出的工具,安装自动推送JS代码的网页,在页面被访问时,页面URL将立即被推送给百度


    06

    此时要注意,有些 hexo 主题集成了这项功能,比如 next 主题,在 themes\next\layout_scripts\ 下有个 baidu_push.swig 文件,我们只需要把如下代码粘贴到该文件,然后在主题配置文件设置 baidu_push: true 即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    {% if theme.baidu_push %}
    <script>
    (function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
    bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
    }
    else {
    bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
    })();
    </script>
    {% endif %}

    然而大部分主题是没有集成这项功能的,对于大部分主题来说,我们可以把以下代码粘贴到 head.ejs 文件的 <head></head> 标签之间即可,从而实现自动推送(比如我使用的是 Material X 主题,那么只需要把代码粘贴到 \themes\material-x\layout\_partial\head.ejs 中即可)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script>
    (function(){
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
    bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
    }
    else {
    bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
    })();
    </script>

    ● sitemap

    首先我们要使用以下命令生成一个网站地图:

    1
    2
    npm install hexo-generator-sitemap --save     
    npm install hexo-generator-baidu-sitemap --save

    这里也注意一下,将根目录的 _config.yml 文件,大约第 17 行处,url 改为在百度站长平台添加的域名,也就是你网站的首页地址:

    1
    2
    3
    4
    # URL
    url: https://www.itrhx.com
    root: /
    permalink: :year/:month/:day/:title/

    然后使用命令 hexo g -d 将网站部署上去,然后访问 你的首页/sitemap.xml 或者 你的首页/baidusitemap.xml 就可以看到网站地图了

    比如我的是:https://www.itrhx.com/baidusitemap.xml 或者 https://www.itrhx.com/sitemap.xml

    其中 sitemap.xml 文件是搜索引擎通用的 sitemap 文件,baidusitemap.xml 是百度专用的 sitemap 文件

    然后来到百度站长平台的 sitemap 提交页面,将你的 sitemap 地址提交即可,如果成功的话状态会显示为正常,初次提交要等几分钟,sitemap.xml 相比 baidusitemap.xml 来说等待时间也会更长,如果以后你博客有新的文章或其他页面,可以点击手动更新文件,更新一下新的 sitemap


    07

    ● 手动提交

    手动提交不需要其他额外操作,直接把需要收录的页面的 url 提交即可,这种方法效率较低,更新较慢,不推荐使用


    08

    ● 提交谷歌搜索

    提交谷歌搜索引擎比较简单,在提交之前,我们依然可以使用 site:域名 查看网站是否被收录,我的网站搭建了有差不多一年了,之前也没提交过收录,不过谷歌爬虫的确是强大,即使没有提交过,现在也能看到有一百多条结果了:


    09

    接下来我们将网站提交谷歌搜索引擎搜索,进入谷歌站长平台,登录你的谷歌账号之后会让你验证网站所有权:


    10

    有两种验证方式,分别是网域和网址前缀,两种资源类型区别如下:

    网址前缀资源
    网域资源
    说明仅包含具有指定前缀(包括协议 http/https)的网址。如果希望资源匹配任何协议或子网域(http/https/www./m. 等),建议改为添加网域资源。包括所有子网域(m、www 等)和多种协议(http、https、ftp)的网域级资源。
    验证多种类型仅 DNS 记录验证
    示例资源 http://example.com/

    http://example.com/dresses/1234
    X https://example.com/dresses/1234
    X http://www.example.com/dresses/1234
    资源 example.com

    http://example.com/dresses/1234
    https://example.com/dresses/1234
    http://www.example.com/dresses/1234
    http://support.m.example.com/dresses/1234

    由对比可知选择网域资源验证方式比较好,只需要一个域名就可以匹配到多种格式的 URL,之后会给你一个 TXT 的记录值,复制它到你域名 DNS 增加一个 TXT 记录,点击验证即可


    11

    提交谷歌收录比较简单,选择站点地图,将我们之前生成的 sitemap 提交就行了,过几分钟刷新一下看到成功字样表示提交成功!


    12
    ]]>
    - - - - - Hexo - - - - - - - Hexo - - SEO - - - -
    - - - - - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS - - /2019/09/16/A47-hexo-deployed-to-github-and-coding/ - -

    部署到 Coding Pages 的好处:国内访问速度更快,可以提交百度收录(GitHub 禁止了百度的爬取)

    部署到 Coding Pages 的坏处:就今年来说,Coding 不太稳定,随时有宕机的可能,群里的朋友已经经历过几次了,不过相信以后会越来越稳定的

    部署过程中常见的问题:无法实现全站 HTTPS,Coding 申请 SSL 证书失败,浏览器可能会提示不是安全链接

    本文前提:你已经将 Hexo 成功部署到了 GitHub Pages,如果还没有,请参考:《使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】》

    本文将全面讲述如何成功双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS,同时解决一些常见的问题!


    1.创建项目

    进入 Coding 官网,点击个人版登陆,没有账号就注册一个并登录,由于 Coding 已经被腾讯收购了,所以登录就会来到腾讯云开发者平台,点击创建项目


    01

    项目名称建议和你的用户名一致,这样做的好处是:到时候可以直接通过 user_name.coding.me 访问你的博客,如果项目名与用户名不一致,则需要通过 user_name.coding.me/project_name 才能访问,项目描述可以随便写


    02

    2.配置公钥

    配置 SSH 公钥方法与 GitHub Pages 的方式差不多,点击你的头像,依次选择【个人设置】-【SSH公钥】-【新增公钥】


    03

    前面部署到 GitHub Pages 的时候就已经有了一对公钥,我们直接将该公钥粘贴进去就行,公钥名称可以随便写,选中永久有效选项

    PS:公钥储存位置一般在 C:\Users\用户名\.ssh 目录下的 id_rsa.pub 文件里,用记事本打开复制其内容即可


    04

    添加公钥后,我们可以右键 Get Bash,输入以下命令来检查是否配置成功:

    1
    ssh -T git@git.coding.net

    若出现以下提示,则证明配置成功:

    1
    2
    Coding 提示: Hello XXX, You've connected to Coding.net via SSH. This is a personal key.
    XXX,你好,你已经通过 SSH 协议认证 Coding.net 服务,这是一个个人公钥

    3.配置 _config.yml

    进入你的项目,在右下角有选择连接方式,选择 SSH 方式(HTTPS 方式也可以,但是这种方式有时候可能连接不上,SSH 连接不容易出问题),一键复制,然后打开你本地博客根目录的 _config.yml 文件,找到 deploy 关键字,添加 coding 地址:coding: git@git.dev.tencent.com:user_name/user_name.git,也就是刚刚复制的 SSH 地址


    05

    06

    添加完成后先执行命令 hexo clean 清理一下缓存,然后执行命令 hexo g -d 将博客双线部署到 Coding Pages 和 GitHub Pages,如下图所示表示部署成功:


    13

    4.开启 Coding Pages

    进入你的项目,在代码栏下选择 Pages 服务,一键开启 Coding Pages,等待几秒后刷新网页即可看到已经开启的 Coding Pages,到目前为止,你就可以通过 xxxx.coding.me(比如我的是 trhx.coding.me)访问你的 Coding Pages 页面了


    07

    08

    5.绑定域名并开启 HPPTS

    首先在你的域名 DNS 设置中添加一条 CNAME 记录指向 xxxx.coding.me,解析路线选择 默认,将 GitHub 的解析路线改为 境外,这样境外访问就会走 GitHub,境内就会走 Coding,也有人说阿里云是智能解析,自动分配路线,如果解析路线都是默认,境外访问同样会智能选择走 GitHub,境内走 Coding,我没有验证过,有兴趣的可以自己试试,我的解析如下图所示:


    09

    然后点击静态 Pages 应用右上角的设置,进入设置页面,这里要注意,如果你之前已经部署到了 GitHub Pages 并开启了 HTTPS,那么直接在设置页面绑定你自己的域名,SSL/TLS 安全证书就会显示申请错误,如下图所示,没有申请到 SSL 证书,当你访问你的网站时,浏览器就会提示不是安全连接


    10

    申请错误原因是:在验证域名所有权时会定位到 Github Pages 的主机上导致 SSL 证书申请失败

    正确的做法是:先去域名 DNS 把 GitHub 的解析暂停掉,然后再重新申请 SSL 证书,大约十秒左右就能申请成功,然后开启强制 HTTPS 访问

    这里也建议同时绑定有 www 前缀和没有 www 前缀的,如果要绑定没有 www 前缀的,首先要去域名 DNS 添加一个 A 记录,主机记录为 @,记录值为你博客 IP 地址,IP 地址可以在 cmd 命令行 ping 一下得到,然后在 Coding Pages 中设置其中一个为【首选】,另一个设置【跳转至首选】,这样不管用户是否输入 www 前缀都会跳到有 www 前缀的了

    在博客资源引用的时候也要注意所有资源的 URL 必须是以 https:// 开头,不然浏览器依旧会提示不安全!


    13

    11_1

    至此,我们的 Hexo 博客就成功双线部署到 Coding Pages 和 GitHub Pages 了,并且也实现了全站 HPPTS,最后来一张 GitHub Pages 和 Coding Pages 在国内的速度对比图,可以明显看到速度的提升


    12
    ]]>
    - - - - - Hexo - - - - - - - Hexo - - Coding Pages - - GitHub Pages - - - -
    - - - - - Python3 爬虫学习笔记 C16 - - /2019/09/14/A46-Python3-spider-C16/ - -
    Python3 爬虫学习笔记第十六章 —— 【数据储存系列 — Redis】

    【16.1】关于 Redis

    Redis 是一个基于内存的高效的键值型(key-value)非关系型数据库,它支持存储的 value 类型非常多,包括 string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合) 和 hash(哈希类型),它的性能十分优越,可以支持每秒十几万此的读/写操作,其性能远超数据库,并且还支持集群、分布式、主从同步等配置,原则上可以无限扩展,让更多的数据存储在内存中,此外,它还支持一定的事务能力,这保证了高并发的场景下数据的安全和一致性。


    【16.2】使用 Redis

    首先安装 Redis 和 redis-py 库,管理 Redis 可以使用可视化工具 Redis Desktop Manager,该工具现在收费了,分享个 0.8.8.384 的免费版本

    安装 redis-py 库:pip install redis
    Redis 官网:https://redis.io
    官方文档:https://redis.io/documentation
    中文官网:http://www.redis.cn
    中文教程:http://www.runoob.com/redis/redis-tutorial.html
    GitHub:https://github.com/antirez/redis
    Redis Windows下载地址一:https://github.com/microsoftarchive/redis/releases (最新版 3.2.100,似乎不再更新)
    Redis Windows下载地址二:https://github.com/tporadowski/redis/releases (最新版)
    Redis Desktop Manager 官网:https://redisdesktop.com/
    Redis Desktop Manager 0.8.8.384 免费版:https://pan.baidu.com/s/18MKeCqT0MG0hc89jfkpIkA (提取码:3ovc)

    利用 Python 连接 Redis 示例:

    1
    2
    3
    4
    5
    from redis import StrictRedis

    redis = StrictRedis(host='localhost', port=6379, db=0, password='000000')
    redis.set('name', 'TRHX')
    print(redis.get('name'))

    传入 Redis 的地址、运行端口、使用的数据库和密码, 4 个参数默认值分别为 localhost、6379、0 和 None,声明一个 StrictRedis 对象,调用 set() 方法,设置一个键值对,输出结果如下:

    1
    b'TRHX'

    另外也可以使用 ConnectionPool 来连接:

    1
    2
    3
    4
    from redis import StrictRedis, ConnectionPool  

    pool = ConnectionPool(host='localhost', port=6379, db=0, password='000000')
    redis = StrictRedis(connection_pool=pool)

    ConnectionPool 也支持通过 URL 来构建:

    1
    2
    3
    redis://[:password]@host:port/db  # 创建 Redis TCP 连接
    rediss://[:password]@host:port/db # 创建 Redis TCP+SSL 连接
    unix://[:password]@/path/to/socket.sock?db=db # 创建 Redis UNIX socket 连接

    代码示例:

    1
    2
    3
    4
    5
    from redis import StrictRedis, ConnectionPool

    url = 'redis://:000000@localhost:6379/0'
    pool = ConnectionPool.from_url(url)
    redis = StrictRedis(connection_pool=pool)

    以下是有关的键操作、字符串操作、列表操作、集合操作、散列操作的各种方法,记录一下,方便查阅
    来源:《Python3 网络爬虫开发实战(崔庆才著)》
    Redis 命令参考:http://redisdoc.com/http://doc.redisfans.com/


    【16.3】Key(键)操作

    方法作用参数说明示例示例说明示例结果
    exists(name)判断一个键是否存在name:键名redis.exists(‘name’)是否存在 name 这个键True
    delete(name)删除一个键name:键名redis.delete(‘name’)删除 name 这个键1
    type(name)判断键类型name:键名redis.type(‘name’)判断 name 这个键类型b’string’
    keys(pattern)获取所有符合规则的键pattern:匹配规则redis.keys(‘n*’)获取所有以 n 开头的键[b’name’]
    randomkey()获取随机的一个键randomkey()获取随机的一个键b’name’
    rename(src, dst)重命名键src:原键名;dst:新键名redis.rename(‘name’, ‘nickname’)将 name 重命名为 nicknameTrue
    dbsize()获取当前数据库中键的数目dbsize()获取当前数据库中键的数目100
    expire(name, time)设定键的过期时间,单位为秒name:键名;time:秒数redis.expire(‘name’, 2)将 name 键的过期时间设置为 2 秒True
    ttl(name)获取键的过期时间,单位为秒,-1 表示永久不过期name:键名redis.ttl(‘name’)获取 name 这个键的过期时间-1
    move(name, db)将键移动到其他数据库name:键名;db:数据库代号move(‘name’, 2)将 name 移动到 2 号数据库True
    flushdb()删除当前选择数据库中的所有键flushdb()删除当前选择数据库中的所有键True
    flushall()删除所有数据库中的所有键flushall()删除所有数据库中的所有键True

    【16.4】String(字符串)操作

    方法作用参数说明示例示例说明示例结果
    set(name, value)给数据库中键名为 name 的 string 赋予值 valuename:键名;value:值redis.set(‘name’, ‘Bob’)给 name 这个键的 value 赋值为 BobTrue
    get(name)返回数据库中键名为 name 的 string 的 valuename:键名redis.get(‘name’)返回 name 这个键的 valueb’Bob’
    getset(name, value)给数据库中键名为 name 的 string 赋予值 value 并返回上次的 valuename:键名;value:新值redis.getset(‘name’, ‘Mike’)赋值 name 为 Mike 并得到上次的 valueb’Bob’
    mget(keys, *args)返回多个键对应的 value 组成的列表keys:键名序列redis.mget([‘name’, ‘nickname’])返回 name 和 nickname 的 value[b’Mike’, b’Miker’]
    setnx(name, value)如果不存在这个键值对,则更新 value,否则不变name:键名redis.setnx(‘newname’, ‘James’)如果 newname 这个键不存在,则设置值为 James第一次运行结果是 True,第二次运行结果是 False
    setex(name, time, value)设置可以对应的值为 string 类型的 value,并指定此键值对应的有效期name:键名;time:有效期;value:值redis.setex(‘name’, 1, ‘James’)将 name 这个键的值设为 James,有效期为 1 秒True
    setrange(name, offset, value)设置指定键的 value 值的子字符串name:键名;offset:偏移量;value:值redis.set(‘name’, ‘Hello’) redis.setrange (‘name’, 6, ‘World’)设置 name 为 Hello 字符串,并在 index 为 6 的位置补 World11,修改后的字符串长度
    mset(mapping)批量赋值mapping:字典或关键字参数redis.mset({‘name1’: ‘Durant’, ‘name2’: ‘James’})将 name1 设为 Durant,name2 设为 JamesTrue
    msetnx(mapping)键均不存在时才批量赋值mapping:字典或关键字参数redis.msetnx({‘name3’: ‘Smith’, ‘name4’: ‘Curry’})在 name3 和 name4 均不存在的情况下才设置二者值True
    incr(name, amount=1)键名为 name 的 value 增值操作,默认为 1,键不存在则被创建并设为 amountname:键名;amount:增长的值redis.incr(‘age’, 1)age 对应的值增 1,若不存在,则会创建并设置为 11,即修改后的值
    decr(name, amount=1)键名为 name 的 value 减值操作,默认为 1,键不存在则被创建并将 value 设置为 - amountname:键名;amount:减少的值redis.decr(‘age’, 1)age 对应的值减 1,若不存在,则会创建并设置为-1-1,即修改后的值
    append(key, value)键名为 key 的 string 的值附加 valuekey:键名redis.append(‘nickname’, ‘OK’)向键名为 nickname 的值后追加 OK13,即修改后的字符串长度
    substr(name, start, end=-1)返回键名为 name 的 string 的子字符串name:键名;start:起始索引;end:终止索引,默认为-1,表示截取到末尾redis.substr(‘name’, 1, 4)返回键名为 name 的值的字符串,截取索引为 1~4 的字符b’ello’
    getrange(key, start, end)获取键的 value 值从 start 到 end 的子字符串key:键名;start:起始索引;end:终止索引redis.getrange(‘name’, 1, 4)返回键名为 name 的值的字符串,截取索引为 1~4 的字符b’ello

    【16.5】Hash(哈希表)操作

    方法作用参数说明示例示例说明示例结果
    hset(name, key, value)向键名为 name 的散列表中添加映射name:键名;key:映射键名;value:映射键值hset(‘price’, ‘cake’, 5)向键名为 price 的散列表中添加映射关系,cake 的值为 51,即添加的映射个数
    hsetnx(name, key, value)如果映射键名不存在,则向键名为 name 的散列表中添加映射name:键名;key:映射键名;value:映射键值hsetnx(‘price’, ‘book’, 6)向键名为 price 的散列表中添加映射关系,book 的值为 61,即添加的映射个数
    hget(name, key)返回键名为 name 的散列表中 key 对应的值name:键名;key:映射键名redis.hget(‘price’, ‘cake’)获取键名为 price 的散列表中键名为 cake 的值5
    hmget(name, keys, *args)返回键名为 name 的散列表中各个键对应的值name:键名;keys:键名序列redis.hmget(‘price’, [‘apple’, ‘orange’])获取键名为 price 的散列表中 apple 和 orange 的值[b’3’, b’7’]
    hmset(name, mapping)向键名为 name 的散列表中批量添加映射name:键名;mapping:映射字典redis.hmset(‘price’, {‘banana’: 2, ‘pear’: 6})向键名为 price 的散列表中批量添加映射True
    hincrby(name, key, amount=1)将键名为 name 的散列表中映射的值增加 amountname:键名;key:映射键名;amount:增长量redis.hincrby(‘price’, ‘apple’, 3)key 为 price 的散列表中 apple 的值增加 36,修改后的值
    hexists(name, key)键名为 name 的散列表中是否存在键名为键的映射name:键名;key:映射键名redis.hexists(‘price’, ‘banana’)键名为 price 的散列表中 banana 的值是否存在True
    hdel(name, *keys)在键名为 name 的散列表中,删除键名为键的映射name:键名;keys:键名序列redis.hdel(‘price’, ‘banana’)从键名为 price 的散列表中删除键名为 banana 的映射True
    hlen(name)从键名为 name 的散列表中获取映射个数name:键名redis.hlen(‘price’)从键名为 price 的散列表中获取映射个数6
    hkeys(name)从键名为 name 的散列表中获取所有映射键名name:键名redis.hkeys(‘price’)从键名为 price 的散列表中获取所有映射键名[b’cake’, b’book’, b’banana’, b’pear’]
    hvals(name)从键名为 name 的散列表中获取所有映射键值name:键名redis.hvals(‘price’)从键名为 price 的散列表中获取所有映射键值[b’5’, b’6’, b’2’, b’6’]
    hgetall(name)从键名为 name 的散列表中获取所有映射键值对name:键名redis.hgetall(‘price’)从键名为 price 的散列表中获取所有映射键值对{b’cake’: b’5’, b’book’: b’6’, b’orange’: b’7’, b’pear’: b’6’}

    【16.6】List(列表)操作

    方法作用参数说明示例示例说明示例结果
    rpush(name, *values)在键名为 name 的列表末尾添加值为 value 的元素,可以传多个name:键名;values:值redis.rpush(‘list’, 1, 2, 3)向键名为 list 的列表尾添加 1、2、33,列表大小
    lpush(name, *values)在键名为 name 的列表头添加值为 value 的元素,可以传多个name:键名;values:值redis.lpush(‘list’, 0)向键名为 list 的列表头部添加 04,列表大小
    llen(name)返回键名为 name 的列表的长度name:键名redis.llen(‘list’)返回键名为 list 的列表的长度4
    lrange(name, start, end)返回键名为 name 的列表中 start 至 end 之间的元素name:键名;start:起始索引;end:终止索引redis.lrange(‘list’, 1, 3)返回起始索引为 1 终止索引为 3 的索引范围对应的列表[b’3’, b’2’, b’1’]
    ltrim(name, start, end)截取键名为 name 的列表,保留索引为 start 到 end 的内容name:键名;start:起始索引;end:终止索引ltrim(‘list’, 1, 3)保留键名为 list 的索引为 1 到 3 的元素True
    lindex(name, index)返回键名为 name 的列表中 index 位置的元素name:键名;index:索引redis.lindex(‘list’, 1)返回键名为 list 的列表索引为 1 的元素b’2’
    lset(name, index, value)给键名为 name 的列表中 index 位置的元素赋值,越界则报错name:键名;index:索引位置;value:值redis.lset(‘list’, 1, 5)将键名为 list 的列表中索引为 1 的位置赋值为 5True
    lrem(name, count, value)删除 count 个键的列表中值为 value 的元素name:键名;count:删除个数;value:值redis.lrem(‘list’, 2, 3)将键名为 list 的列表删除两个 31,即删除的个数
    lpop(name)返回并删除键名为 name 的列表中的首元素name:键名redis.lpop(‘list’)返回并删除名为 list 的列表中的第一个元素b’5’
    rpop(name)返回并删除键名为 name 的列表中的尾元素name:键名redis.rpop(‘list’)返回并删除名为 list 的列表中的最后一个元素b’2’
    blpop(keys, timeout=0)返回并删除名称在 keys 中的 list 中的首个元素,如果列表为空,则会一直阻塞等待keys:键名序列;timeout:超时等待时间,0 为一直等待redis.blpop(‘list’)返回并删除键名为 list 的列表中的第一个元素[b’5’]
    brpop(keys, timeout=0)返回并删除键名为 name 的列表中的尾元素,如果 list 为空,则会一直阻塞等待keys:键名序列;timeout:超时等待时间,0 为一直等待redis.brpop(‘list’)返回并删除名为 list 的列表中的最后一个元素[b’2’]
    rpoplpush(src, dst)返回并删除名称为 src 的列表的尾元素,并将该元素添加到名称为 dst 的列表头部src:源列表的键;dst:目标列表的 keyredis.rpoplpush(‘list’, ‘list2’)将键名为 list 的列表尾元素删除并将其添加到键名为 list2 的列表头部,然后返回b’2’

    【16.7】Set(集合)操作

    方法作用参数说明示例示例说明示例结果
    sadd(name, *values)向键名为 name 的集合中添加元素name:键名;values:值,可为多个redis.sadd(‘tags’, ‘Book’, ‘Tea’, ‘Coffee’)向键名为 tags 的集合中添加 Book、Tea 和 Coffee 这 3 个内容3,即插入的数据个数
    srem(name, *values)从键名为 name 的集合中删除元素name:键名;values:值,可为多个redis.srem(‘tags’, ‘Book’)从键名为 tags 的集合中删除 Book1,即删除的数据个数
    spop(name)随机返回并删除键名为 name 的集合中的一个元素name:键名redis.spop(‘tags’)从键名为 tags 的集合中随机删除并返回该元素b’Tea’
    smove(src, dst, value)从 src 对应的集合中移除元素并将其添加到 dst 对应的集合中src:源集合;dst:目标集合;value:元素值redis.smove(‘tags’, ‘tags2’, ‘Coffee’)从键名为 tags 的集合中删除元素 Coffee 并将其添加到键为 tags2 的集合True
    scard(name)返回键名为 name 的集合的元素个数name:键名redis.scard(‘tags’)获取键名为 tags 的集合中的元素个数3
    sismember(name, value)测试 member 是否是键名为 name 的集合的元素name:键值redis.sismember(‘tags’, ‘Book’)判断 Book 是否是键名为 tags 的集合元素True
    sinter(keys, *args)返回所有给定键的集合的交集keys:键名序列redis.sinter([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的交集{b’Coffee’}
    sinterstore(dest, keys, *args)求交集并将交集保存到 dest 的集合dest:结果集合;keys:键名序列redis.sinterstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的交集并将其保存为 inttag1
    sunion(keys, *args)返回所有给定键的集合的并集keys:键名序列redis.sunion([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的并集{b’Coffee’, b’Book’, b’Pen’}
    sunionstore(dest, keys, *args)求并集并将并集保存到 dest 的集合dest:结果集合;keys:键名序列redis.sunionstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的并集并将其保存为 inttag3
    sdiff(keys, *args)返回所有给定键的集合的差集keys:键名序列redis.sdiff([‘tags’, ‘tags2’])返回键名为 tags 的集合和键名为 tags2 的集合的差集{b’Book’, b’Pen’}
    sdiffstore(dest, keys, *args)求差集并将差集保存到 dest 集合dest:结果集合;keys:键名序列redis.sdiffstore (‘inttag’, [‘tags’, ‘tags2’])求键名为 tags 的集合和键名为 tags2 的集合的差集并将其保存为 inttag3
    smembers(name)返回键名为 name 的集合的所有元素name:键名redis.smembers(‘tags’)返回键名为 tags 的集合的所有元素{b’Pen’, b’Book’, b’Coffee’}
    srandmember(name)随机返回键名为 name 的集合中的一个元素,但不删除元素name:键值redis.srandmember(‘tags’)随机返回键名为 tags 的集合中的一个元素Srandmember (name)

    【16.8】SortedSet(有序集合)操作

    方法作用参数说明示例示例说明示例结果
    zadd(name, args, *kwargs)向键名为 name 的 zset 中添加元素 member,score 用于排序。如果该元素存在,则更新其顺序name:键名;args:可变参数redis.zadd(‘grade’, 100, ‘Bob’, 98, ‘Mike’)向键名为 grade 的 zset 中添加 Bob(其 score 为 100),并添加 Mike(其 score 为 98)2,即添加的元素个数
    zrem(name, *values)删除键名为 name 的 zset 中的元素name:键名;values:元素redis.zrem(‘grade’, ‘Mike’)从键名为 grade 的 zset 中删除 Mike1,即删除的元素个数
    zincrby(name, value, amount=1)如果在键名为 name 的 zset 中已经存在元素 value,则将该元素的 score 增加 amount;否则向该集合中添加该元素,其 score 的值为 amountname:键名;value:元素;amount:增长的 score 值redis.zincrby(‘grade’, ‘Bob’, -2)键名为 grade 的 zset 中 Bob 的 score 减 298.0,即修改后的值
    zrank(name, value)返回键名为 name 的 zset 中元素的排名,按 score 从小到大排序,即名次name:键名;value:元素值redis.zrank(‘grade’, ‘Amy’)得到键名为 grade 的 zset 中 Amy 的排名1
    zrevrank(name, value)返回键为 name 的 zset 中元素的倒数排名(按 score 从大到小排序),即名次name:键名;value:元素值redis.zrevrank (‘grade’, ‘Amy’)得到键名为 grade 的 zset 中 Amy 的倒数排名2
    zrevrange(name, start, end, withscores= False)返回键名为 name 的 zset(按 score 从大到小排序)中 index 从 start 到 end 的所有元素name:键值;start:开始索引;end:结束索引;withscores:是否带 scoreredis.zrevrange (‘grade’, 0, 3)返回键名为 grade 的 zset 中前四名元素[b’Bob’, b’Mike’, b’Amy’, b’James’]
    zrangebyscore (name, min, max, start=None, num=None, withscores=False)返回键名为 name 的 zset 中 score 在给定区间的元素name:键名;min:最低 score;max:最高 score;start:起始索引;num:个数;withscores:是否带 scoreredis.zrangebyscore (‘grade’, 80, 95)返回键名为 grade 的 zset 中 score 在 80 和 95 之间的元素[b’Bob’, b’Mike’, b’Amy’, b’James’]
    zcount(name, min, max)返回键名为 name 的 zset 中 score 在给定区间的数量name:键名;min:最低 score;max:最高 scoreredis.zcount(‘grade’, 80, 95)返回键名为 grade 的 zset 中 score 在 80 到 95 的元素个数2
    zcard(name)返回键名为 name 的 zset 的元素个数name:键名redis.zcard(‘grade’)获取键名为 grade 的 zset 中元素的个数3
    zremrangebyrank (name, min, max)删除键名为 name 的 zset 中排名在给定区间的元素name:键名;min:最低位次;max:最高位次redis.zremrangebyrank (‘grade’, 0, 0)删除键名为 grade 的 zset 中排名第一的元素1,即删除的元素个数
    zremrangebyscore (name, min, max)删除键名为 name 的 zset 中 score 在给定区间的元素name:键名;min:最低 score;max:最高 scoreredis.zremrangebyscore (‘grade’, 80, 90)删除 score 在 80 到 90 之间的元素1,即删除的元素个数

    【16.9】RedisDump

    RedisDump 是 Redis 一个数据导入导出工具,是基于 Ruby 实现的,首先访问 Ruby 官网安装对应操作系统的 Ruby:http://www.ruby-lang.org/zh_cn/downloads/ ,安装完成即可使用 gem 命令,该命令类似于 Python 当中的 pip 命令,使用 gem install redis-dump 即可完成 RedisDump 的安装,安装完成后就可以使用导出数据 redis-dump 命令和导入数据 redis-load 命令了


    【16.9.1】导出数据 redis-dump

    在命令行输入 redis-dump -h 可以查看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Usage: E:/Ruby26-x64/bin/redis-dump [global options] COMMAND [command options]
    -u, --uri=S Redis URI (e.g. redis://hostname[:port])
    -d, --database=S Redis database (e.g. -d 15)
    -a, --password=S Redis password (e.g. -a 'my@pass/word')
    -s, --sleep=S Sleep for S seconds after dumping (for debugging)
    -c, --count=S Chunk size (default: 10000)
    -f, --filter=S Filter selected keys (passed directly to redis' KEYS command)
    -b, --base64 Encode key values as base64 (useful for binary values)
    -O, --without_optimizations Disable run time optimizations
    -V, --version Display version
    -D, --debug
    --nosafe

    命令解释:

    • -u Redis 连接字符串
    • -d 数据库代号
    • -a 数据库密码
    • -s 导出之后的休眠时间
    • -c 分块大小,默认是 10000
    • -f 导出时的过滤器
    • -b 将键值编码为 base64(对二进制值有用)
    • -O 禁用运行时优化
    • -V 显示版本
    • -D 开启调试

    导出数据示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    redis-dump

    # 指定端口
    redis-dump -u 127.0.0.1:6379

    # 指定端口和密码
    redis-dump -u :password@127.0.0.1:6379

    # 导出指定数据库
    redis-dump -u 127.0.0.1:6379 -d 3

    # 导出包含特定值的数据
    redis-dump -u 127.0.0.1:6379 -f age

    输出示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # 导出所有数据
    {"db":0,"key":"name5","ttl":-1,"type":"string","value":"DDD","size":3}
    {"db":0,"key":"name2","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":0,"key":"name4","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name6","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name","ttl":-1,"type":"string","value":"TRHX","size":4}
    {"db":0,"key":"name3","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":2,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":2,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":3,"key":"name2","ttl":-1,"type":"string","value":"HHH","size":3}
    {"db":3,"key":"name1","ttl":-1,"type":"string","value":"RRR","size":3}
    {"db":4,"key":"age","ttl":-1,"type":"string","value":"20","size":2}
    {"db":4,"key":"age2","ttl":-1,"type":"string","value":"19","size":2}

    # 导出 3 号数据库
    {"db":3,"key":"name2","ttl":-1,"type":"string","value":"HHH","size":3}
    {"db":3,"key":"name1","ttl":-1,"type":"string","value":"RRR","size":3}

    # 导出 key 包含 age 的数据
    {"db":4,"key":"age","ttl":-1,"type":"string","value":"20","size":2}
    {"db":4,"key":"age2","ttl":-1,"type":"string","value":"19","size":2}

    导出所有数据为 JSON 文件:

    1
    redis-dump -u 127.0.0.1:6379 > db_full.json

    该命令将会在当前目录生成一个名为 db_full.json 的文件,文件内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    {"db":0,"key":"name5","ttl":-1,"type":"string","value":"DDD","size":3}
    {"db":0,"key":"name2","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":0,"key":"name4","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name6","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name","ttl":-1,"type":"string","value":"TRHX","size":4}
    {"db":0,"key":"name3","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":2,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":2,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":3,"key":"name2","ttl":-1,"type":"string","value":"HHH","size":3}
    {"db":3,"key":"name1","ttl":-1,"type":"string","value":"RRR","size":3}
    {"db":4,"key":"age","ttl":-1,"type":"string","value":"20","size":2}
    {"db":4,"key":"age2","ttl":-1,"type":"string","value":"19","size":2}

    使用参数 -d 指定某个数据库的所有数据导出为 JSON 文件:

    1
    redis-dump -u 127.0.0.1:6379 -d 4 > db_db4.json

    该命令会将 4 号数据库的数据导出到 db_db4.json 文件:

    1
    2
    {"db":4,"key":"age","ttl":-1,"type":"string","value":"20","size":2}
    {"db":4,"key":"age2","ttl":-1,"type":"string","value":"19","size":2}

    使用参数 -f 过滤数据,只导出特定的数据:

    1
    redis-dump -u 127.0.0.1:6379 -f name > db_name.json

    该命令会导出 key 包含 name 的数据到 db_name.json 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {"db":0,"key":"name5","ttl":-1,"type":"string","value":"DDD","size":3}
    {"db":0,"key":"name2","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":0,"key":"name4","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name6","ttl":-1,"type":"string","value":"CCC","size":3}
    {"db":0,"key":"name","ttl":-1,"type":"string","value":"TRHX","size":4}
    {"db":0,"key":"name3","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":1,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":2,"key":"name2","ttl":-1,"type":"string","value":"BBB","size":3}
    {"db":2,"key":"name1","ttl":-1,"type":"string","value":"AAA","size":3}
    {"db":3,"key":"name2","ttl":-1,"type":"string","value":"HHH","size":3}
    {"db":3,"key":"name1","ttl":-1,"type":"string","value":"RRR","size":3}

    【16.9.2】导入数据 redis-load

    在命令行输入 redis-load -h 可以查看:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    redis-load --help  
    Try: redis-load [global options] COMMAND [command options]
    -u, --uri=S Redis URI (e.g. redis://hostname[:port])
    -d, --database=S Redis database (e.g. -d 15)
    -s, --sleep=S Sleep for S seconds after dumping (for debugging)
    -n, --no_check_utf8
    -V, --version Display version
    -D, --debug
    --nosafe

    命令解释:

    • -u Redis 连接字符串
    • -d 数据库代号,默认是全部
    • -s 导出之后的休眠时间
    • -n 不检测 UTF-8 编码
    • -V 显示版本
    • -D 开启调试

    导入示例:

    1
    2
    3
    4
    5
    # 将 test.json 文件所有内容导入到数据库
    < test.json redis-load -u 127.0.0.1:6379

    # 将 test.json 文件 db 值为 6 的数据导入到数据库
    < test.json redis-load -u 127.0.0.1:6379 -d 6

    另外,以下方法也能导入数据:

    1
    2
    3
    4
    5
    # 将 test.json 文件所有内容导入到数据库
    cat test.json | redis-load -u 127.0.0.1:6379

    # 将 test.json 文件 db 值为 6 的数据导入到数据库
    cat test.json | redis-load -u 127.0.0.1:6379 -d 6

    注意:cat 是 Linux 系统专有的命令,在 Windows 系统里没有 cat 这个命令,可以使用 Windows 批处理命令 type 代替 cat

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - Redis - - - -
    - - - - - Python3 爬虫学习笔记 C15 - - /2019/09/10/A45-Python3-spider-C15/ - -
    Python3 爬虫学习笔记第十五章 —— 【代理的基本使用】

    【15.1】代理初识

    大多数网站都有反爬虫机制,如果一段时间内同一个 IP 发送的请求过多,服务器就会拒绝访问,直接禁封该 IP,此时,设置代理即可解决这个问题,网络上有许多免费代理和付费代理,比如西刺代理全网代理 IP快代理等,设置代理需要用到的就是代理 IP 地址和端口号,如果电脑上装有代理软件(例如:酸酸乳SSR),软件一般会在本机创建 HTTP 或 SOCKS 代理服务,直接使用此代理也可以

    【15.2】urllib 库使用代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from urllib.error import URLError
    from urllib.request import ProxyHandler, build_opener

    proxy = '127.0.0.1:1080'
    proxy_handler = ProxyHandler({
    'http': 'http://' + proxy,
    'https': 'https://' + proxy
    })
    opener = build_opener(proxy_handler)
    try:
    response = opener.open('http://httpbin.org/get')
    print(response.read().decode('utf8'))
    except URLError as e:
    print(e.reason)

    http://httpbin.org/get 是一个请求测试站点,借助 ProxyHandler 设置代理,参数为字典类型,键名为协议类型,键值为代理,代理的写法:proxy = '127.0.0.1:1080',其中 127.0.0.1 为 IP 地址,1080 为端口号,这里表示本机的代理软件已经在本地 1080 端口创建了代理服务,代理前面需要加上 http 或者 https 协议,当请求的链接为 http 协议时,ProxyHandler 会自动调用 http 代理,同理,当请求的链接为 https 协议时,ProxyHandler 会自动调用 https 代理,build_opener() 方法传入 ProxyHandler 对象来创建一个 opener,调用 open() 方法传入一个 url 即可通过代理访问该链接,运行结果为一个 JSON,origin 字段为此时客户端的 IP

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    "args": {},
    "headers": {
    "Accept-Encoding": "identity",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.6"
    },
    "origin": "168.70.60.141, 168.70.60.141",
    "url": "https://httpbin.org/get"
    }

    如果是需要认证的代理,只需要在代理前面加入代理认证的用户名密码即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from urllib.error import URLError
    from urllib.request import ProxyHandler, build_opener

    proxy = 'username:password@127.0.0.1:1080'
    proxy_handler = ProxyHandler({
    'http': 'http://' + proxy,
    'https': 'https://' + proxy
    })
    opener = build_opener(proxy_handler)
    try:
    response = opener.open('http://httpbin.org/get')
    print(response.read().decode('utf8'))
    except URLError as e:
    print(e.reason)

    如果代理是 SOCKS5 类型,需要用到 socks 模块,设置代理方法如下:

    扩展:SOCKS5 是一个代理协议,它在使用TCP/IP协议通讯的前端机器和服务器机器之间扮演一个中介角色,使得内部网中的前端机器变得能够访问 Internet 网中的服务器,或者使通讯更加安全

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import socks
    import socket
    from urllib import request
    from urllib.error import URLError

    socks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)
    socket.socket = socks.socksocket
    try:
    response = request.urlopen('http://httpbin.org/get')
    print(response.read().decode('utf-8'))
    except URLError as e:
    print(e.reason)

    【15.3】requests 库使用代理

    requests 库使用代理只需要传入 proxies 参数即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import requests

    proxy = '127.0.0.1:1080'
    proxies = ({
    'http': 'http://' + proxy,
    'https': 'https://' + proxy
    })
    try:
    response = requests.get('http://httpbin.org/get', proxies=proxies)
    print(response.text)
    except requests.exceptions.ChunkedEncodingError as e:
    print('Error', e.args)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    {
    "args": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "origin": "168.70.60.141, 168.70.60.141",
    "url": "https://httpbin.org/get"
    }

    同样的,如果是需要认证的代理,也只需要在代理前面加入代理认证的用户名密码即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import requests

    proxy = 'username:password@127.0.0.1:1080'
    proxies = ({
    'http': 'http://' + proxy,
    'https': 'https://' + proxy
    })
    try:
    response = requests.get('http://httpbin.org/get', proxies=proxies)
    print(response.text)
    except requests.exceptions.ChunkedEncodingError as e:
    print('Error', e.args)

    如果代理是 SOCKS5 类型,需要用到 requests[socks] 模块或者 socks 模块,使用 requests[socks] 模块时设置代理方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import requests

    proxy = '127.0.0.1:1080'
    proxies = {
    'http': 'socks5://' + proxy,
    'https': 'socks5://' + proxy
    }
    try:
    response = requests.get('http://httpbin.org/get', proxies=proxies)
    print(response.text)
    except requests.exceptions.ConnectionError as e:
    print('Error', e.args)

    使用 socks 模块时设置代理方法如下(此类方法为全局设置):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import requests
    import socks
    import socket

    socks.set_default_proxy(socks.SOCKS5, '127.0.0.1', 1080)
    socket.socket = socks.socksocket
    try:
    response = requests.get('http://httpbin.org/get')
    print(response.text)
    except requests.exceptions.ConnectionError as e:
    print('Error', e.args)

    【15.4】Selenium 使用代理

    【15.4.1】Chrome

    1
    2
    3
    4
    5
    6
    7
    8
    from selenium import webdriver

    proxy = '127.0.0.1:1080'
    chrome_options = webdriver.ChromeOptions()
    chrome_options.add_argument('--proxy-server=http://' + proxy)
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    browser.get('http://httpbin.org/get')

    通过 ChromeOptions 来设置代理,在创建 Chrome 对象的时候用 chrome_options 参数传递即可,访问目标链接后显示如下信息:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    {
    "args": {},
    "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9",
    "Host": "httpbin.org",
    "Upgrade-Insecure-Requests": "1",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
    },
    "origin": "168.70.60.141, 168.70.60.141",
    "url": "https://httpbin.org/get"
    }

    如果是认证代理,则设置方法如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    import zipfile

    ip = '127.0.0.1'
    port = 1080
    username = 'username'
    password = 'password'

    manifest_json = """{"version":"1.0.0","manifest_version": 2,"name":"Chrome Proxy","permissions": ["proxy","tabs","unlimitedStorage","storage","<all_urls>","webRequest","webRequestBlocking"],"background": {"scripts": ["background.js"]
    }
    }
    """

    background_js ="""
    var config = {
    mode: "fixed_servers",
    rules: {
    singleProxy: {
    scheme: "http",
    host: "%(ip) s",
    port: %(port) s
    }
    }
    }

    chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});

    function callbackFn(details) {
    return {
    authCredentials: {username: "%(username) s",
    password: "%(password) s"
    }
    }
    }

    chrome.webRequest.onAuthRequired.addListener(
    callbackFn,
    {urls: ["<all_urls>"]},
    ['blocking']
    )
    """ % {'ip': ip, 'port': port, 'username': username, 'password': password}

    plugin_file = 'proxy_auth_plugin.zip'
    with zipfile.ZipFile(plugin_file, 'w') as zp:
    zp.writestr("manifest.json", manifest_json)
    zp.writestr("background.js", background_js)
    chrome_options = Options()
    chrome_options.add_argument("--start-maximized")
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options.add_extension(plugin_file)
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    browser.get('http://httpbin.org/get')

    需要在本地创建一个 manifest.json 配置文件和 background.js 脚本来设置认证代理。运行代码之后本地会生成一个 proxy_auth_plugin.zip 文件来保存当前配置

    【15.4.1】PhantomJS

    借助 service_args 参数,也就是命令行参数即可设置代理:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium import webdriver

    service_args = [
    '--proxy=127.0.0.1:1080',
    '--proxy-type=http'
    ]
    path = r'F:\PycharmProjects\Python3爬虫\phantomjs-2.1.1\bin\phantomjs.exe'
    browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)
    browser.get('http://httpbin.org/get')
    print(browser.page_source)

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <html><head></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">{
    "args": {},
    "headers": {
    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,en,*",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"
    },
    "origin": "168.70.60.141, 168.70.60.141",
    "url": "https://httpbin.org/get"
    }
    </pre></body></html>

    如果是需要认证的代理,只需要在 service_args 参数加入 –proxy-auth 选项即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from selenium import webdriver

    service_args = [
    '--proxy=127.0.0.1:1080',
    '--proxy-type=http',
    '--proxy-auth=username:password'
    ]
    path = r'F:\PycharmProjects\Python3爬虫\phantomjs-2.1.1\bin\phantomjs.exe'
    browser = webdriver.PhantomJS(executable_path=path, service_args=service_args)
    browser.get('http://httpbin.org/get')
    print(browser.page_source)
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 代理 - - - -
    - - - - - Python3 爬虫学习笔记 C14 - - /2019/09/08/A44-Python3-spider-C14/ - -
    Python3 爬虫学习笔记第十四章 —— 【验证码对抗系列 — 点触验证码】

    【14.1】关于点触验证码

    点触验证码是由杭州微触科技有限公司研发的新一代的互联网验证码,使用点击的形式完成验证,采用专利的印刷算法以及加密算法,保证每次请求到的验证图具有极高的安全性,常见的点触验证码如下:


    01

    【14.2】点触验证码攻克思路

    点触验证码相对其他类型验证码比较复杂,如果依靠 OCR 图像识别点触验证码,则识别难度非常大,此时就要用到互联网的验证码服务平台,这些服务平台全部都是人工在线识别,准确率非常高,原理就是先将验证码图片提交给平台,平台会返回识别结果在图片中的坐标位置,然后我们再解析坐标模拟点击即可,常见的打码平台有超级鹰、云打码等,打码平台是收费的,拿超级鹰来说,1元 = 1000题分,识别一次验证码将花费一定的题分,不同类型验证码需要的题分不同,验证码越复杂所需题分越高,比如 7 位中文汉字需要 70 题分,常见 4 ~ 6 位英文数字只要 10 题分,其他打码平台价格也都差不多

    以下以超级鹰打码平台中国铁路12306官网来做练习


    【14.3】模拟登录 12306 — 总体思路

    首先在超级鹰打码平台注册账号并申请一个软件 ID,官网:http://www.chaojiying.com/ ,先充值一块钱得到 1000 题分,观察 12306 官网,发现验证码是要我们点击所有满足条件的图片,一般有 1~4 张图片满足要求,由此可确定在超级鹰打码平台的验证码类型为 9004(坐标多选,返回1~4个坐标,如:x1,y1|x2,y2|x3,y3), 获取其 Python API:http://www.chaojiying.com/download/Chaojiying_Python.rar ,然后用 Selenium 模拟登陆,获取到验证码,并将验证码发送给超级鹰后台,返回识别图片的坐标,最后模拟点击即可,整个过程的实现由主程序 12306.py 和超级鹰 API chaojiying.py 组成

    整个程序包含的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def __init__(): 初始化 WebDriver、Chaojiying 对象等
    def crack(): 破解入口、获取、识别验证码、模拟登录
    def open(): 账号密码输入
    def get_screenshot(): 整个页面截图
    def get_touclick_element(): 获取验证码位置
    def get_position(): 获取验证码坐标
    def get_touclick_image(): 剪裁验证码部分
    def get_points(self, captcha_result): 分析超级鹰返回的坐标
    def touch_click_words(self, locations): 模拟点击符合要求的图片
    def login(self): 点击登陆按钮,完成模拟登录

    整个程序用到的库:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import time
    from io import BytesIO
    from PIL import Image
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from chaojiying import Chaojiying
    from selenium.common.exceptions import TimeoutException

    【14.4】主函数

    1
    2
    3
    if __name__ == '__main__':
    crack = CrackTouClick()
    crack.crack()

    【14.5】初始化函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    USERNAME = '155********'
    PASSWORD = '***********'

    CHAOJIYING_USERNAME = '*******'
    CHAOJIYING_PASSWORD = '*******'
    CHAOJIYING_SOFT_ID = '********'
    CHAOJIYING_KIND = '9004'


    class CrackTouClick():
    def __init__(self):
    self.url = 'https://kyfw.12306.cn/otn/resources/login.html'
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    self.wait = WebDriverWait(self.browser, 20)
    self.email = USERNAME
    self.password = PASSWORD
    self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)

    定义 12306 账号(USERNAME)、密码(PASSWORD)、超级鹰用户名(CHAOJIYING_USERNAME)、超级鹰登录密码(CHAOJIYING_PASSWORD)、超级鹰软件 ID(CHAOJIYING_SOFT_ID)、验证码类型(CHAOJIYING_KIND),登录链接 url:https://kyfw.12306.cn/otn/resources/login.html ,谷歌浏览器驱动的目录(path),浏览器启动参数,并将相关参数传递给超级鹰 API


    【14.6】破解入口函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    def crack(self):
    self.open()
    image = self.get_touclick_image()
    bytes_array = BytesIO()
    image.save(bytes_array, format='PNG')
    result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND)
    print(result)
    locations = self.get_points(result)
    self.touch_click_words(locations)
    self.login()
    try:
    success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '用户姓名'))
    print(success)
    cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name')
    print(cc.text)
    except TimeoutException:
    self.chaojiying.ReportError(result['pic_id'])
    self.crack()

    调用 open() 函数输入账号密码

    调用 get_touclick_image() 函数获取验证码图片

    利用超级鹰 Python API PostPic() 方法即可把图片发送给超级鹰后台,发送的图像是字节流格式,返回的结果是一个 JSON,如果识别成功,典型的返回结果类似于:{'err_no': 0, 'err_str': 'OK', 'pic_id': '6002001380949200001', 'pic_str': '132,127|56,77', 'md5': '1f8e1d4bef8b11484cb1f1f34299865b'},其中,pic_str 就是识别的文字的坐标,是以字符串形式返回的,每个坐标都以 | 分隔

    调用 get_points() 函数解析超级鹰识别结果

    调用 touch_click_words() 函数对符合要求的图片进行点击,然后点击登陆按钮模拟登陆

    使用 try-except 语句判断是否出现了用户信息,判断依据是是否有用户姓名的出现,出现的姓名和实际姓名一致则登录成功,如果失败了就重试,超级鹰会返回该分值


    【14.7】账号密码输入函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def open(self):
    self.browser.get(self.url)
    login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account')))
    login.click()
    time.sleep(3)
    username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName')))
    password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password')))
    username.send_keys(self.email)
    password.send_keys(self.password)

    分析页面可知,登陆页面 URL 为:https://kyfw.12306.cn/otn/resources/login.html ,该页面默认出现的是扫描二维码登陆,所以要先点击账号登录,找到该 CSS 元素为 login-hd-account,调用 click() 方法实现模拟点击,此时出现账号密码输入框,同样找到其 ID 分别为 J-userNameJ-password,调用 send_keys() 方法输入账号密码


    【14.8】页面截图函数

    1
    2
    3
    4
    def get_screenshot(self):
    screenshot = self.browser.get_screenshot_as_png()
    screenshot = Image.open(BytesIO(screenshot))
    return screenshot

    对整个页面进行截图


    【14.9】验证码元素查找函数

    1
    2
    3
    def get_touclick_element(self):
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    return element

    同样分析页面,验证码所在位置的 CSS 为 login-pwd-code


    【14.10】获取验证码坐标函数

    1
    2
    3
    4
    5
    6
    7
    def get_position(self):
    element = self.get_touclick_element()
    time.sleep(3)
    location = element.location
    size = element.size
    top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
    return (top, bottom, left, right)

    location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x 轴向右递增,y 轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息


    【14.11】验证码剪裁函数

    1
    2
    3
    4
    5
    6
    def get_touclick_image(self, name='12306.png'):
    top, bottom, left, right = self.get_position()
    screenshot = self.get_screenshot()
    captcha = screenshot.crop((left, top, right, bottom))
    captcha.save(name)
    return captcha

    根据验证码的坐标信息,对页面截图进行剪裁,得到验证码部分,将其保存为 12306.png


    【14.12】验证码坐标解析函数

    1
    2
    3
    4
    def get_points(self, captcha_result):
    groups = captcha_result.get('pic_str').split('|')
    locations = [[int(number) for number in group.split(',')] for group in groups]
    return locations

    get_points() 方法将超级鹰的验证码识别结果变成列表的形式


    【14.13】验证码模拟点击函数

    1
    2
    3
    4
    def touch_click_words(self, locations):
    for location in locations:
    print(location)
    ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform()

    touch_click_words() 方法通过调用 move_to_element_with_offset() 方法依次传入解析后的坐标,点击即可


    【14.14】模拟点击登陆函数

    1
    2
    3
    def login(self):
    submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login')))
    submit.click()

    分析页面,找到登陆按钮的 ID 为 J-login,调用 click() 方法模拟点击按钮实现登录


    【14.15】效果实现动图

    最终实现效果图:(关键信息已经过打码处理)


    02

    【14.16】完整代码

    12306.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    import time
    from io import BytesIO
    from PIL import Image
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from chaojiying import Chaojiying_Client
    from selenium.common.exceptions import TimeoutException

    USERNAME = '155********'
    PASSWORD = '***********'

    CHAOJIYING_USERNAME = '***********'
    CHAOJIYING_PASSWORD = '***********'
    CHAOJIYING_SOFT_ID = '******'
    CHAOJIYING_KIND = '9004'


    class CrackTouClick():
    def __init__(self): #登陆
    self.url = 'https://kyfw.12306.cn/otn/resources/login.html'
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    self.browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    self.wait = WebDriverWait(self.browser, 20)
    self.email = USERNAME
    self.password = PASSWORD
    self.chaojiying = Chaojiying_Client(CHAOJIYING_USERNAME, CHAOJIYING_PASSWORD, CHAOJIYING_SOFT_ID)

    def crack(self):
    self.open()
    image = self.get_touclick_image()
    bytes_array = BytesIO()
    image.save(bytes_array, format='PNG')
    result = self.chaojiying.PostPic(bytes_array.getvalue(), CHAOJIYING_KIND)
    print(result)
    locations = self.get_points(result)
    self.touch_click_words(locations)
    self.login()
    try:
    success = self.wait.until(EC.text_to_be_present_in_element((By.CSS_SELECTOR, '.welcome-name'), '谭仁侯'))
    print(success)
    cc = self.browser.find_element(By.CSS_SELECTOR, '.welcome-name')
    print(cc.text)

    except TimeoutException:
    self.chaojiying.ReportError(result['pic_id'])
    self.crack()

    def open(self):
    self.browser.get(self.url)
    login = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-hd-account')))
    login.click()
    time.sleep(3)
    username = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-userName')))
    password = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'input#J-password')))
    username.send_keys(self.email)
    password.send_keys(self.password)

    def get_screenshot(self):
    screenshot = self.browser.get_screenshot_as_png()
    screenshot = Image.open(BytesIO(screenshot))
    return screenshot

    def get_touclick_element(self):
    element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, '.login-pwd-code')))
    return element

    def get_position(self):
    element = self.get_touclick_element()
    time.sleep(3)
    location = element.location
    size = element.size
    top, bottom, left, right = location['y'], location['y'] + size['height'], location['x'], location['x'] + size['width']
    return (top, bottom, left, right)

    def get_touclick_image(self, name='12306.png'):
    top, bottom, left, right = self.get_position()
    screenshot = self.get_screenshot()
    captcha = screenshot.crop((left, top, right, bottom))
    captcha.save(name)
    return captcha

    def get_points(self, captcha_result):
    groups = captcha_result.get('pic_str').split('|')
    locations = [[int(number) for number in group.split(',')] for group in groups]
    return locations

    def touch_click_words(self, locations):
    for location in locations:
    print(location)
    ActionChains(self.browser).move_to_element_with_offset(self.get_touclick_element(), location[0]/1.25, location[1]/1.25).click().perform()

    def login(self):
    submit = self.wait.until(EC.element_to_be_clickable((By.ID, 'J-login')))
    submit.click()


    if __name__ == '__main__':
    crack = CrackTouClick()
    crack.crack()

    chaojiying.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import requests
    from hashlib import md5


    class Chaojiying_Client(object):

    def __init__(self, username, password, soft_id):
    self.username = username
    password = password.encode('utf8')
    self.password = md5(password).hexdigest()
    self.soft_id = soft_id
    self.base_params = {
    'user': self.username,
    'pass2': self.password,
    'softid': self.soft_id,
    }
    self.headers = {
    'Connection': 'Keep-Alive',
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0)',
    }

    def PostPic(self, im, codetype):
    """
    im: 图片字节
    codetype: 题目类型 参考 http://www.chaojiying.com/price.html
    """
    params = {
    'codetype': codetype,
    }
    params.update(self.base_params)
    files = {'userfile': ('ccc.jpg', im)}
    r = requests.post('http://upload.chaojiying.net/Upload/Processing.php', data=params, files=files, headers=self.headers)
    return r.json()

    def ReportError(self, im_id):
    """
    im_id:报错题目的图片ID
    """
    params = {
    'id': im_id,
    }
    params.update(self.base_params)
    r = requests.post('http://upload.chaojiying.net/Upload/ReportError.php', data=params, headers=self.headers)
    return r.json()
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 点触验证码 - - - -
    - - - - - Python3 爬虫学习笔记 C13 - - /2019/09/07/A43-Python3-spider-C13/ - -
    Python3 爬虫学习笔记第十三章 —— 【验证码对抗系列 — 滑动验证码】

    【13.1】关于滑动验证码

    滑动验证码属于行为式验证码,需要通过用户的操作行为来完成验证,一般是根据提示用鼠标将滑块拖动到指定的位置完成验证,此类验证码背景图片采用多种图像加密技术,且添加了很多随机效果,能有效防止OCR文字识别,另外,验证码上的文字采用了随机印刷技术,能够随机采用多种字体、多种变形的实时随机印刷,防止暴力破解;斗鱼、哔哩哔哩、淘宝等平台都使用了滑动验证码


    01

    【13.2】滑动验证码攻克思路

    利用自动化测试工具 Selenium 直接模拟人的行为方式来完成验证,首先要分析页面,想办法找到滑动验证码的完整图片、带有缺口的图片和需要滑动的图片,通过对比原始的图片和带滑块缺口的图片的像素,像素不同的地方就是缺口位置,计算出滑块缺口的位置,得到所需要滑动的距离,最后利用 Selenium 进行对滑块的拖拽,拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功

    以下以哔哩哔哩为例来做模拟登录练习


    【13.3】模拟登录 bilibili — 总体思路

    首先使用 Selenium 模拟登陆 bilibili,自动输入账号密码,查找到登陆按钮并点击,使其出现滑动验证码,此时分析页面,滑动验证组件是由3个 canvas 组成,分别代表完整图片、带有缺口的图片和需要滑动的图片,3个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,分别获取三张图片时要将其他两张图片设置为 display:none,获取元素位置后即可对图片截图并保存,通过图片像素对比,找到缺口位置即为滑块要移动的距离,随后构造滑动轨迹,按照先加速后减速的方式移动滑块完成验证。

    整个程序包含的函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    def init(): 初始化函数,定义全局变量
    def login(): 登录函数,输入账号密码并点击登录
    def find_element(): 验证码元素查找函数,查找三张图的元素
    def hide_element(): 设置元素不可见函数
    def show_element(): 设置元素可见函数
    def save_screenshot(): 验证码截图函数,截取三张图并保存
    def slide(): 滑动函数
    def is_pixel_equal(): 像素判断函数,寻找缺口位置
    def get_distance(): 计算滑块移动距离函数
    def get_track(): 构造移动轨迹函数
    def move_to_gap(): 模拟拖动函数

    整个程序用到的库:

    1
    2
    3
    4
    5
    6
    7
    8
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    from selenium.webdriver import ActionChains
    import time
    import random

    【13.4】主函数

    1
    2
    3
    4
    5
    if __name__ == '__main__':
    init()
    login()
    find_element()
    slide()

    【13.5】初始化函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def init():
    global url, browser, username, password, wait
    url = 'https://passport.bilibili.com/login'
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    username = '155********'
    password = '***********'
    wait = WebDriverWait(browser, 20)

    global 关键字定义了全局变量,随后是登录页面url、谷歌浏览器驱动的目录path、实例化 Chrome 浏览器、设置浏览器分辨率最大化、用户名、密码、WebDriverWait() 方法设置等待超时


    【13.6】登录函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def login():
    browser.get(url)
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    user.send_keys(username)
    passwd.send_keys(password)
    login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))
    time.sleep(random.random() * 3)
    login_btn.click()

    等待用户名输入框和密码输入框对应的 ID 节点加载出来,分析页面可知,用户名输入框 id="login-username",密码输入框 id="login-passwd",获取这两个节点,调用 send_keys() 方法输入用户名和密码,随后获取登录按钮,分析页面可知登录按钮 class="btn btn-login",随机产生一个数并将其扩大三倍作为暂停时间,最后调用 click() 方法实现登录按钮的点击


    【13.7】验证码元素查找函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    def find_element():
    c_background = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))
    c_slice = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))
    c_full_bg = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))
    hide_element(c_slice)
    save_screenshot(c_background, 'back')
    show_element(c_slice)
    save_screenshot(c_slice, 'slice')
    show_element(c_full_bg)
    save_screenshot(c_full_bg, 'full')

    我们要获取验证码的三张图片,分别是完整的图片、带有缺口的图片和需要滑动的图片,分析页面代码,这三张图片是由 3 个 canvas 组成,3 个 canvas 元素包含 CSS display 属性,display:block 为可见,display:none 为不可见,在分别获取三张图片时要将其他两张图片设置为 display:none,这样做才能单独提取到每张图片,定位三张图片的 class 分别为:带有缺口的图片(c_background):geetest_canvas_bg geetest_absolute、需要滑动的图片(c_slice):geetest_canvas_slice geetest_absolute、完整图片(c_full_bg):geetest_canvas_fullbg geetest_fade geetest_absolute,随后传值给 save_screenshot() 函数,进一步对验证码进行处理


    02

    【13.8】元素可见性设置函数

    1
    2
    3
    4
    5
    6
    7
    8
    # 设置元素不可见
    def hide_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")


    # 设置元素可见
    def show_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")

    【13.9】验证码截图函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    def save_screenshot(obj, name):
    try:
    pic_url = browser.save_screenshot('.\\bilibili.png')
    print("%s:截图成功!" % pic_url)
    left = obj.location['x']
    top = obj.location['y']
    right = left + obj.size['width']
    bottom = top + obj.size['height']
    print('图:' + name)
    print('Left %s' % left)
    print('Top %s' % top)
    print('Right %s' % right)
    print('Bottom %s' % bottom)
    print('')
    im = Image.open('.\\bilibili.png')
    im = im.crop((left, top, right, bottom))
    file_name = 'bili_' + name + '.png'
    im.save(file_name)
    except BaseException as msg:
    print("%s:截图失败!" % msg)

    location 属性可以返回该图片对象在浏览器中的位置,坐标轴是以屏幕左上角为原点,x轴向右递增,y轴向下递增,size 属性可以返回该图片对象的高度和宽度,由此可以得到验证码的位置信息,首先调用 save_screenshot() 属性对整个页面截图并保存,然后向 crop() 方法传入验证码的位置信息,由位置信息再对验证码进行剪裁并保存


    【13.10】滑动函数

    1
    2
    3
    4
    5
    6
    def slide():
    distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))
    print('计算偏移量为:%s Px' % distance)
    trace = get_trace(distance - 5)
    move_to_gap(trace)
    time.sleep(3)

    get_distance() 函数传入完整的图片和缺口图片,计算滑块需要滑动的距离,再把距离信息传入 get_trace() 函数,构造滑块的移动轨迹,最后根据轨迹信息调用 move_to_gap() 函数移动滑块完成验证


    【13.11】计算滑块移动距离函数

    1
    2
    3
    4
    5
    6
    def get_distance(bg_image, fullbg_image):
    distance = 60
    for i in range(distance, fullbg_image.size[0]):
    for j in range(fullbg_image.size[1]):
    if not is_pixel_equal(fullbg_image, bg_image, i, j):
    return i

    get_distance() 方法即获取缺口位置的方法,此方法的参数是两张图片,一张为完整的图片,另一张为带缺口的图片,distance 为滑块的初始位置,遍历两张图片的每个像素,利用 is_pixel_equal() 像素判断函数判断两张图片同一位置的像素是否相同,比较两张图 RGB 的绝对值是否均小于定义的阈值 threshold,如果绝对值均在阈值之内,则代表像素点相同,继续遍历,否则代表不相同的像素点,即缺口的位置


    【13.12】像素判断函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def is_pixel_equal(bg_image, fullbg_image, x, y):
    bg_pixel = bg_image.load()[x, y]
    fullbg_pixel = fullbg_image.load()[x, y]
    threshold = 60
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(
    bg_pixel[2] - fullbg_pixel[2] < threshold)):
    return True
    else:
    return False

    将完整图片和缺口图片两个对象分别赋值给变量 bg_image和 fullbg_image,接下来对比图片获取缺口。我们在这里遍历图片的每个坐标点,获取两张图片对应像素点的 RGB 数据,判断像素的各个颜色之差,abs() 用于取绝对值,如果二者的 RGB 数据差距在一定范围内,那就代表两个像素相同,继续比对下一个像素点,如果差距超过一定范围,则代表像素点不同,当前位置即为缺口位置


    【13.13】构造移动轨迹函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def get_trace(distance):
    trace = []
    faster_distance = distance * (4 / 5)
    start, v0, t = 0, 0, 0.1
    while start < distance:
    if start < faster_distance:
    a = 20
    else:
    a = -20
    move = v0 * t + 1 / 2 * a * t * t
    v = v0 + a * t
    v0 = v
    start += move
    trace.append(round(move))
    return trace

    get_trace() 方法传入的参数为移动的总距离,返回的是运动轨迹,运动轨迹用 trace 表示,它是一个列表,列表的每个元素代表每次移动多少距离,利用 Selenium 进行对滑块的拖拽时要模仿人的行为,由于有个对准过程,所以是先快后慢,匀速移动、随机速度移动都不会成功,因此要设置一个加速和减速的距离,这里设置加速距离 faster_distance 是总距离 distance 的4/5倍,滑块滑动的加速度用 a 来表示,当前速度用 v 表示,初速度用 v0 表示,位移用 move 表示,所需时间用 t 表示,它们之间满足以下关系:

    1
    2
    move = v0 * t + 0.5 * a * t * t 
    v = v0 + a * t

    设置初始位置、初始速度、时间间隔分别为0, 0, 0.1,加速阶段和减速阶段的加速度分别设置为20和-20,直到运动轨迹达到总距离时,循环终止,最后得到的 trace 记录了每个时间间隔移动了多少位移,这样滑块的运动轨迹就得到了


    【13.14】模拟拖动函数

    1
    2
    3
    4
    5
    6
    7
    def move_to_gap(trace):
    slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))
    ActionChains(browser).click_and_hold(slider).perform()
    for x in trace:
    ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.5)
    ActionChains(browser).release().perform()

    传入的参数为运动轨迹,首先查找到滑动按钮,然后调用 ActionChains 的 click_and_hold() 方法按住拖动底部滑块,perform() 方法用于执行,遍历运动轨迹获取每小段位移距离,调用 move_by_offset() 方法移动此位移,最后调用 release() 方法松开鼠标即可


    【13.15】效果实现动图

    最终实现效果图:(关键信息已经过打码处理)


    03

    【13.16】完整代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
    from selenium.webdriver.support.wait import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.common.by import By
    from selenium.webdriver import ActionChains
    import time
    import random
    from PIL import Image


    def init():
    global url, browser, username, password, wait
    url = 'https://passport.bilibili.com/login'
    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    chrome_options = Options()
    chrome_options.add_argument('--start-maximized')
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    username = '155********'
    password = '***********'
    wait = WebDriverWait(browser, 20)


    def login():
    browser.get(url)
    user = wait.until(EC.presence_of_element_located((By.ID, 'login-username')))
    passwd = wait.until(EC.presence_of_element_located((By.ID, 'login-passwd')))
    user.send_keys(username)
    passwd.send_keys(password)
    login_btn = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'a.btn.btn-login')))
    time.sleep(random.random() * 3)
    login_btn.click()


    def find_element():
    c_background = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_bg.geetest_absolute')))
    c_slice = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_slice.geetest_absolute')))
    c_full_bg = wait.until(
    EC.presence_of_element_located((By.CSS_SELECTOR, 'canvas.geetest_canvas_fullbg.geetest_fade.geetest_absolute')))
    hide_element(c_slice)
    save_screenshot(c_background, 'back')
    show_element(c_slice)
    save_screenshot(c_slice, 'slice')
    show_element(c_full_bg)
    save_screenshot(c_full_bg, 'full')


    def hide_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: none;")


    def show_element(element):
    browser.execute_script("arguments[0].style=arguments[1]", element, "display: block;")


    def save_screenshot(obj, name):
    try:
    pic_url = browser.save_screenshot('.\\bilibili.png')
    print("%s:截图成功!" % pic_url)
    left = obj.location['x']
    top = obj.location['y']
    right = left + obj.size['width']
    bottom = top + obj.size['height']
    print('图:' + name)
    print('Left %s' % left)
    print('Top %s' % top)
    print('Right %s' % right)
    print('Bottom %s' % bottom)
    print('')
    im = Image.open('.\\bilibili.png')
    im = im.crop((left, top, right, bottom))
    file_name = 'bili_' + name + '.png'
    im.save(file_name)
    except BaseException as msg:
    print("%s:截图失败!" % msg)


    def slide():
    distance = get_distance(Image.open('.\\bili_back.png'), Image.open('.\\bili_full.png'))
    print('计算偏移量为:%s Px' % distance)
    trace = get_trace(distance - 5)
    move_to_gap(trace)
    time.sleep(3)


    def get_distance(bg_image, fullbg_image):
    distance = 60
    for i in range(distance, fullbg_image.size[0]):
    for j in range(fullbg_image.size[1]):
    if not is_pixel_equal(fullbg_image, bg_image, i, j):
    return i


    def is_pixel_equal(bg_image, fullbg_image, x, y):
    bg_pixel = bg_image.load()[x, y]
    fullbg_pixel = fullbg_image.load()[x, y]
    threshold = 60
    if (abs(bg_pixel[0] - fullbg_pixel[0] < threshold) and abs(bg_pixel[1] - fullbg_pixel[1] < threshold) and abs(
    bg_pixel[2] - fullbg_pixel[2] < threshold)):
    return True

    else:
    return False


    def get_trace(distance):
    trace = []
    faster_distance = distance * (4 / 5)
    start, v0, t = 0, 0, 0.1
    while start < distance:
    if start < faster_distance:
    a = 20
    else:
    a = -20
    move = v0 * t + 1 / 2 * a * t * t
    v = v0 + a * t
    v0 = v
    start += move
    trace.append(round(move))
    return trace


    def move_to_gap(trace):
    slider = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'div.geetest_slider_button')))
    ActionChains(browser).click_and_hold(slider).perform()
    for x in trace:
    ActionChains(browser).move_by_offset(xoffset=x, yoffset=0).perform()
    time.sleep(0.5)
    ActionChains(browser).release().perform()


    if __name__ == '__main__':
    init()
    login()
    find_element()
    slide()
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 滑动验证码 - - - -
    - - - - - Python3 爬虫学习笔记 C12 - - /2019/09/05/A42-Python3-spider-C12/ - -
    Python3 爬虫学习笔记第十二章 —— 【验证码对抗系列 — 图形验证码】

    【12.1】关于普通图形验证码

    普通图形验证码一般由四位纯数字、纯字母或者字母数字组合构成,是最常见的验证码,也是最简单的验证码,利用 tesserocr 或者 pytesseract 库即可识别此类验证码,前提是已经安装好 Tesseract-OCR 软件


    01

    【12.2】tesserocr 库识别验证码

    简单示例:

    1
    2
    3
    4
    5
    6
    import tesserocr
    from PIL import Image

    image = Image.open('code.png')
    result = tesserocr.image_to_text(image)
    print(result)

    新建一个 Image 对象,调用 tesserocr 的 image_to_text() 方法,传入 Image 对象即可完成识别,另一种方法:

    1
    2
    import tesserocr
    print(tesserocr.file_to_text('code.png'))

    【12.3】pytesseract 库识别验证码

    简单示例:

    1
    2
    3
    4
    5
    6
    7
    import pytesseract
    from PIL import Image

    img = Image.open('code.png')
    img = img.convert('RGB')
    img.show()
    print(pytesseract.image_to_string(img))

    pytesseract 的各种方法:

    • get_tesseract_version:返回 Tesseract 的版本信息;
    • image_to_string:将图像上的 Tesseract OCR 运行结果返回到字符串;
    • image_to_boxes:返回包含已识别字符及其框边界的结果;
    • image_to_data:返回包含框边界,置信度和其他信息的结果。需要 Tesseract 3.05+;
    • image_to_osd:返回包含有关方向和脚本检测的信息的结果。

    有关参数:

    image_to_data(image, lang='', config='', nice=0, output_type=Output.STRING)

    • image:图像对象;
    • lang:Tesseract 语言代码字符串;
    • config:任何其他配置为字符串,例如:config=’–psm 6’;
    • nice:修改 Tesseract 运行的处理器优先级。Windows不支持。尼斯调整了类似 unix 的流程的优点;
    • output_type:类属性,指定输出的类型,默认为string。

    lang 参数,常见语言代码如下:

    • chi_sim:简体中文
    • chi_tra:繁体中文
    • eng:英文
    • rus:俄罗斯语
    • fra:法语
    • deu:德语
    • jpn:日语

    【12.4】验证码处理

    利用 Image 对象的 convert() 方法传入不同参数可以对验证码做一些额外的处理,如转灰度、二值化等操作,经过处理过后的验证码会更加容易被识别,识别准确度更高,各种参数及含义:

    • 1:1位像素,黑白,每字节一个像素存储;
    • L:8位像素,黑白;
    • P:8位像素,使用调色板映射到任何其他模式;
    • RGB:3x8位像素,真彩色;
    • RGBA:4x8位像素,带透明度掩模的真彩色;
    • CMYK:4x8位像素,分色;
    • YCbCr:3x8位像素,彩色视频格式;
    • I:32位有符号整数像素;
    • F:32位浮点像素。

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    import pytesseract
    from PIL import Image

    image = Image.open('code.png')
    image = image.convert('L')
    image.show()
    result = pytesseract.image_to_string(image)
    print(result)

    Image 对象的 convert() 方法参数传入 L,即可将图片转化为灰度图像,转换前后对比:


    02
    1
    2
    3
    4
    5
    6
    7
    8
    import pytesseract
    from PIL import Image

    image = Image.open('code.png')
    image = image.convert('1')
    image.show()
    result = pytesseract.image_to_string(image)
    print(result)

    Image 对象的 convert() 方法参数传入 1,即可将图片进行二值化处理,处理前后对比:


    03

    【12.5】tesserocr 与 pytesserocr 相关资料

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 图形验证码 - - - -
    - - - - - Python3 爬虫学习笔记 C11 - - /2019/09/04/A41-Python3-spider-C11/ - -
    Python3 爬虫学习笔记第十一章 —— 【MongoDB数据储存】

    【11.1】关于 MongoDB

    MongoDB 属于非关系型数据库,即 NoSQL(Not Only SQL),NoSQL 是基于键值对的,不需要经过 SQL 层的解析,数据之间没有耦合性,性能极高,非关系型数据库分为以下几种:

    • 键值存储数据库:Redis、Voldemort、Oracle BDB 等;
    • 列存储数据库:Cassandra、HBase、Riak 等;
    • 文档型数据库:CouchDB、MongoDB 等;
    • 图形数据库:Neo4J、InfoGrid、Infinite Graph 等。

    【11.2】MongoDB 基本操作语句

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # 创建数据库(如果数据库不存在就创建数据库, 存在就切换到指定的数据库)
    use DATABASE_NAME

    # 查看所有数据库
    show dbs

    # 查看当前所在数据库
    db

    # 删除当前数据库
    db.dropDatabase()

    # 删除集合
    db.COLLECTION_NAME.drop()

    # 创建集合
    db.createCollection("COLLECTION_NAME")

    # 插入文档
    db.COLLECTION_NAME.insert(document)
    db.COLLECTION_NAME.save(document)

    # 更新文档
    db.COLLECTION_NAME.update()

    # 删除文档
    db.COLLECTION_NAME.remove()

    # 查询文档
    db.COLLECTION_NAME.find(query, projection)

    【11.3】连接 MongoDB

    连接 MongoDB 需要导入 pymongo 库,使用 MongoClient() 方法,向其传入地址参数 host 和 端口参数 port 即可

    1
    2
    3
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)

    也可以直接传入 MongoDB 的连接字符串:

    1
    2
    3
    import pymongo

    client = pymongo.MongoClient('mongodb://localhost:27017/')

    【11.4】指定数据库

    使用以下语句皆可指定一个名为 spiders 的数据库:

    1
    db = client.spiders
    1
    db = client['spiders']

    【11.5】指定集合

    MongoDB 的每个数据库包含多个集合(collection),类似于关系型数据库 MySQL 中的数据表,使用以下语句皆可指定一个名为 students 的集合:

    1
    collection = db.students
    1
    collection = db['students']

    【11.6】插入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    students = {
    'id': '17110105',
    'name': 'TRHX',
    'age': 20,
    'gender': 'male'
    }
    result = collection.insert(students)
    print(result)

    在 spiders 数据库的 students 集合里,新建一条学生数据,该数据以字典形式表示,调用 collection 的 insert() 方法插入数据,在 MongoDB 中,每条数据都有一个_id 属性来唯一标识。如果没有显式指明该属性,MongoDB 会自动产生一个 ObjectId 类型的_id 属性。insert() 方法会在执行后返回 _id 值,在 MongoDB 数据库里面可以看到已经成功插入数据,输出结果:

    1
    5d6f1a4b57b65e1547bb3c24

    01

    进阶操作:同时插入多条数据,以列表形式传递:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    students1 = {
    'id': '17110105',
    'name': 'TRHX',
    'age': 20,
    'gender': 'male'
    }
    students2 = {
    'id': '17110106',
    'name': 'AAAA',
    'age': 22,
    'gender': 'male'
    }
    result = collection.insert([students1, students2])
    print(result)

    输出结果:

    1
    [ObjectId('5d6f2be3cd1721962218a709'), ObjectId('5d6f2be3cd1721962218a70a')]

    02

    PyMongo 3.x 及以上版本中,推荐使用 insert_one()insert_many() 方法来分别插入单条记录和多条记录,示例:

    插入单条记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    students = {
    'id': '17110105',
    'name': 'TRHX',
    'age': 20,
    'gender': 'male'
    }
    result = collection.insert_one(students)
    print(result)
    print(result.inserted_id)

    返回的是 InsertOneResult 对象,调用其 inserted_id 属性获取_id:

    1
    2
    <pymongo.results.InsertOneResult object at 0x0000020ED91A5608>
    5d6f73940fe700c5a7ac19f0

    插入多条记录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    students1 = {
    'id': '17110105',
    'name': 'TRHX',
    'age': 20,
    'gender': 'male'
    }
    students2 = {
    'id': '17110106',
    'name': 'AAAA',
    'age': 22,
    'gender': 'male'
    }
    result = collection.insert_many([students1, students2])
    print(result)
    print(result.inserted_ids)

    返回的类型是 InsertManyResult,调用 inserted_ids 属性可以获取插入数据的_id 列表:

    1
    2
    <pymongo.results.InsertManyResult object at 0x0000021698DD36C8>
    [ObjectId('5d6f68598fa881c69b2e0006'), ObjectId('5d6f68598fa881c69b2e0007')]

    【11.6】数据查询

    事先已经创建好 spiders 数据库和 students 集合,包含以下数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    _id:ObjectId("5d6f95d40828142f1dc35fa5")
    id:"17110105"
    name:"TRHX"
    age:20
    gender:"male"

    _id:ObjectId("5d6f95d40828142f1dc35fa6")
    id:"17110106"
    name:"AAA"
    age:20
    gender:"male"

    _id:ObjectId("5d6f95d40828142f1dc35fa7")
    id:"17110107"
    name:"BBB"
    age:19
    gender:"female"

    _id:ObjectId("5d6f95d40828142f1dc35fa8")
    id:"17110108"
    name:"CCC"
    age:22
    gender:"male"

    查询方法一:利用 find_one()find() 方法进行查询, find_one() 查询得到的是单个结果,find() 则返回一个生成器对象

    1
    2
    3
    4
    5
    6
    7
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    result = collection.find_one({'name': 'TRHX'})
    print(result)

    查询 name 为 TRHX 的数据,返回一个字典类型:

    1
    {'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}

    查询方法二:根据 ObjectId 查询,查询时需要使用 bson 库里面的 objectid:

    1
    2
    3
    4
    5
    6
    7
    8
    import pymongo
    from bson.objectid import ObjectId

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    result = collection.find_one({'_id': ObjectId('5d6f95d40828142f1dc35fa7')})
    print(result)

    查询结果:

    1
    {'_id': ObjectId('5d6f95d40828142f1dc35fa7'), 'id': '17110107', 'name': 'BBB', 'age': 19, 'gender': 'female'}

    使用 find() 方法查询多条数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    results = collection.find({'gender': 'male'})
    print(results)
    for result in results:
    print(result)

    find() 方法返回一个生成器对象,遍历得到所有数据,每条数据都是字典类型:

    1
    2
    3
    4
    <pymongo.cursor.Cursor object at 0x00000191F69AAA90>
    {'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}
    {'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}
    {'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'}

    在查询条件中加入比较符号进行查询,以下代码实现了年龄大于等于20的数据查询:

    1
    2
    3
    4
    5
    6
    7
    8
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    results = collection.find({'age': {'$gte': 20}})
    for result in results:
    print(result)

    符号 $gte 表示大于等于,查询结果如下:

    1
    2
    3
    {'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}
    {'_id': ObjectId('5d6f95d40828142f1dc35fa6'), 'id': '17110106', 'name': 'AAA', 'age': 20, 'gender': 'male'}
    {'_id': ObjectId('5d6f95d40828142f1dc35fa8'), 'id': '17110108', 'name': 'CCC', 'age': 22, 'gender': 'male'}
    附表:各种比较符号
    符号含义示例
    $lt小于{‘age’: {‘$lt’: 20}}
    $gt大于{‘age’: {‘$gt’: 20}}
    $lte小于等于{‘age’: {‘$lte’: 20}}
    $gte大于等于{‘age’: {‘$gte’: 20}}
    $ne不等于{‘age’: {‘$ne’: 20}}
    $in在范围内{‘age’: {‘$in’: [20, 23]}}
    $nin不在范围内{‘age’: {‘$nin’: [20, 23]}}

    在查询条件中加入功能符号进行查询,以下代码用正则匹配实现了对名字以 T 开头的学生数据的查询:

    1
    2
    3
    4
    5
    6
    7
    8
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    results = collection.find({'name': {'$regex': '^T.*'}})
    for result in results:
    print(result)

    查询结果:

    1
    {'_id': ObjectId('5d6f95d40828142f1dc35fa5'), 'id': '17110105', 'name': 'TRHX', 'age': 20, 'gender': 'male'}
    附表:各种功能符号
    符号含义示例示例含义
    $regex匹配正则表达式{‘name’: {‘$regex’: ‘^T.*’}}name 以 T 开头
    $exists属性是否存在{‘name’: {‘$exists’: True}}name 属性存在
    $type类型判断{‘age’: {‘$type’: ‘int’}}age 的类型为 int
    $mod数字模操作{‘age’: {‘$mod’: [5, 0]}}年龄模 5 余 0
    $text文本查询{‘$text’: {‘$search’: ‘Mike’}}text 类型的属性中包含 Mike 字符串
    $where高级条件查询{‘$where’: ‘obj.fans_count == obj.follows_count’}自身粉丝数等于关注数

    其他操作:https://docs.mongodb.com/manual/reference/operator/query/

    【11.7】数据计数

    调用 count() 方法可以统计查询结果有多少条数据,输出结果为一个整数:

    1
    2
    3
    4
    5
    6
    7
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    result = collection.find({'name': {'$regex': '^T.*'}}).count()
    print(result)

    【11.8】数据排序

    调用 sort() 方法,向其传入排序的字段及升降序标志即可完成排序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    ascending = collection.find().sort('name', pymongo.ASCENDING)
    descending = collection.find().sort('name', pymongo.DESCENDING)
    print('升序排列:', [result['name'] for result in ascending])
    print('降序排列:', [result['name'] for result in descending])

    输出结果:

    1
    2
    升序排列: ['AAA', 'BBB', 'CCC', 'TRHX']
    降序排列: ['TRHX', 'CCC', 'BBB', 'AAA']

    【11.9】数据偏移

    利用 skip() 方法偏移几个位置,就可以跳过前几条数据,获取偏移量之后的几个数据;利用 limit() 方法指定获取前几条数据:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    ascending = collection.find().sort('name', pymongo.ASCENDING).skip(1)
    descending = collection.find().sort('name', pymongo.DESCENDING).limit(2)
    print('升序排列(偏移量为1,获取后三条数据):', [result['name'] for result in ascending])
    print('降序排列(限制获取前两条数据):', [result['name'] for result in descending])

    输出结果:

    1
    2
    升序排列(偏移量为1,获取后三条数据): ['BBB', 'CCC', 'TRHX']
    降序排列(限制获取前两条数据): ['TRHX', 'CCC']

    【11.10】更新数据

    使用 update() 方法,指定更新的条件和更新后的数据即可:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    condition = {'name': 'TRHX'}
    student = collection.find_one(condition)
    student['age'] = 18
    result = collection.update(condition, student)
    print(result)

    该代码将 name 为 TRHX 的 age 改为了 18,返回结果仍然是字典形式,ok 代表执行成功,nModified 代表影响的数据条数:

    1
    {'n': 1, 'nModified': 1, 'ok': 1.0, 'updatedExisting': True}

    进阶操作:使用 $set 操作符对数据进行更新,指定更新的条件和更新后的数据即可,这样做的好处是:只更新指定的 student 字典内存在的字段,如果原先还有其他字段,则不会更新,也不会删除;如果不用 $set ,则会把之前的数据全部用 student 字典替换,如果原本存在其他字段,则会被删除

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    condition = {'name': 'TRHX'}
    student = collection.find_one(condition)
    student['age'] = 18
    result = collection.update(condition, {'$set': student})
    print(result)

    和插入数据的 insert() 方法一样,在 PyMongo 3.x 版本里,推荐使用 update_one()update_many() 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    condition = {'name': 'TRHX'}
    student = collection.find_one(condition)
    student['age'] = 19
    result = collection.update_one(condition, {'$set': student})
    print(result)
    print(result.matched_count, result.modified_count)

    注意:update_one() 方法不能直接传入修改后的字典,只能使用 {'$set': student} 的形式传入,可以调用 matched_countmodified_count 属性,获取匹配的数据条数和影响的数据条数:

    1
    2
    <pymongo.results.UpdateResult object at 0x00000235A1684508>
    1 1

    使用update_many() 方法可以将所有符合条件的数据都更新:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    condition = {'age': {'$gt': 18}}
    result = collection.update_many(condition, {'$set': {'age': 25}})
    print(result)
    print(result.matched_count, result.modified_count)

    匹配所有年龄大于 18 的数据,更新条件为将这些所有满足条件的年龄都设置成 25,输出结果如下:

    1
    2
    <pymongo.results.UpdateResult object at 0x00000285CECC45C8>
    4 4

    【11.11】删除数据

    调用 remove() 方法并指定删除的条件,此时符合条件的所有数据均会被删除

    1
    2
    3
    4
    5
    6
    7
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    result = collection.remove({'name': 'CCC'})
    print(result)

    输出结果:

    1
    {'n': 1, 'ok': 1.0}

    同样的,在 PyMongo 3.x 版本里,推荐使用 delete_one()delete_many() 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import pymongo

    client = pymongo.MongoClient(host='localhost', port=27017)
    db = client.spiders
    collection = db.students
    result = collection.delete_one({'name': 'AAA'})
    print(result)
    print(result.deleted_count)
    result = collection.delete_many({'gender': 'female'})
    print(result.deleted_count)

    调用 deleted_count 属性可以获取删除的数据条数,输出结果:

    1
    2
    3
    <pymongo.results.DeleteResult object at 0x0000024441B245C8>
    1
    1

    PyMongo 官方文档:http://api.mongodb.com/python/current/api/pymongo/collection.html

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - MongoDB - - - -
    - - - - - Python3 爬虫学习笔记 C10 - - /2019/09/03/A40-Python3-spider-C10/ - -
    Python3 爬虫学习笔记第十章 —— 【MySQL数据储存】

    【10.1】MySQL 基本操作语句

    安装完 MySQL 后,打开 MySQL x.x Command Line Client - Unicode,输入密码即可登录 MySQL,也可在 MySQL 安装目录下打开 cmd 使用命令登录数据库

    数据库操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    # 连接数据库
    mysql -u root -p

    # 退出数据库
    exit

    # 查看所有的数据库
    SHOW DATABASES;

    # 创建一个数据库
    CREATE DATABASE X;

    # 删除一个数据库
    DROP DATABASE IF EXISTS X;

    # 使用这个数据库
    USE X;

    表操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    # 查看所有的表
    SHOW TABLES ;

    # 创建一个表
    CREATE TABLE n(id INT, name VARCHAR(10));
    CREATE TABLE m(id INT, name VARCHAR(10), PRIMARY KEY (id), FOREIGN KEY (id) REFERENCES n(id), UNIQUE (name));
    CREATE TABLE m(id INT, name VARCHAR(10));

    # 直接将查询结果导入或复制到新创建的表
    CREATE TABLE n SELECT * FROM m;

    # 新创建的表与一个存在的表的数据结构类似
    CREATE TABLE m LIKE n;

    # 创建一个临时表
    # 临时表将在你连接MySQL期间存在。当断开连接时,MySQL将自动删除表并释放所用的空间。也可手动删除。
    CREATE TEMPORARY TABLE l(id INT, name VARCHAR(10));

    # 直接将查询结果导入或复制到新创建的临时表
    CREATE TEMPORARY TABLE tt SELECT * FROM n;

    # 删除一个存在表
    DROP TABLE IF EXISTS m;

    # 更改存在表的名称
    ALTER TABLE n RENAME m;
    RENAME TABLE n TO m;

    # 查看表的结构(以下五条语句效果相同)
    DESC n;
    DESCRIBE n;
    SHOW COLUMNS IN n;
    SHOW COLUMNS FROM n;
    EXPLAIN n;

    # 查看表的创建语句
    SHOW CREATE TABLE n;

    表的结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    # 添加字段
    ALTER TABLE n ADD age VARCHAR(2);

    # 添加字段时设定位置
    ALTER TABLE n ADD age VARCHAR(2) FIRST;
    ALTER TABLE n ADD age VARCHAR(2) AFTER name;

    # 修改字段在表中的位置
    ALTER TABLE n MODIFY age VARCHAR(2) AFTER name;

    # 删除字段
    ALTER TABLE n DROP age;

    # 更改字段属性和属性
    ALTER TABLE n CHANGE age a INT;

    # 只更改字段属性
    ALTER TABLE n MODIFY age VARCHAR(7) ;

    # 改变表的存储引擎
    ALTER TABLE t ENGINE myisam;
    ALTER TABLE t ENGINE innodb;

    # 设定自增 初始为1,只能一个字段使用,该字段为主键的一部分
    ALTER TABLE t AUTO_INCREMENT = 0;

    表的数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    # 增加数据
    INSERT INTO n VALUES (1, 'tom', '23'), (2, 'john', '22');
    INSERT INTO n SELECT * FROM n; # 把数据复制一遍重新插入

    # 删除数据
    DELETE FROM n WHERE id = 2;

    # 更改数据
    UPDATE n SET name = 'tom' WHERE id = 2;

    # 数据查找
    SELECT * FROM n WHERE name LIKE '%h%';

    # 数据排序(反序)
    SELECT * FROM n ORDER BY name, id DESC ;

    【10.2】Python 连接 MySQL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import pymysql

    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306)
    cursor = db.cursor()
    cursor.execute('SELECT VERSION()')
    data = cursor.fetchone()
    print('Database version:', data)
    cursor.execute("CREATE DATABASE spiders DEFAULT CHARACTER SET utf8mb4")
    db.close()

    通过 PyMySQL 的 connect 方法声明一个 MySQL 连接对象 db,当前 MySQL 数据库运行在本地,设定 host='localhost',用户名为 root,登录密码为 000000,运行在 3306 端口,调用 cursor() 方法获得 MySQL 的操作游标,该游标用来执行 SQL 语句,通过游标操作 execute() 方法写入 SQL 语句,第一条 SQL 语句获取 MySQL 的版本信息,调用 fetchone() 方法获得第一条数据,即 MySQL 的版本号。第二条 SQL 语句执行创建 spiders 数据库的操作,编码为 utf8mb4,运行该段代码将输出 MySQL 的版本号:

    1
    Database version: ('8.0.17',)

    【10.3】创建表

    1
    2
    3
    4
    5
    6
    7
    import pymysql

    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'CREATE TABLE IF NOT EXISTS students (id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, age VARCHAR(255) NOT NULL, PRIMARY KEY (id))'
    cursor.execute(sql)
    db.close()

    该段代码实现了在 spiders 数据库里创建了一个名为 students 的表,包含 id、name、age 三个字段,类型依次为 varchar、varchar、int

    【10.4】插入数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import pymysql

    id = '17110105'
    user = 'TRH'
    age = 20
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO students(id, name, age) values(%s, %s, %s)'
    try:
    cursor.execute(sql, (id, user, age))
    db.commit()
    except:
    db.rollback()
    db.close()

    commit() 方法的作用是实现数据插入,是真正将语句提交到数据库执行的方法,使用 try except 语句实现异常处理,如果执行失败,则调用 rollback() 方法执行数据回滚,保证原数据不被破坏,使用查询语句可以看到已经插入的数据:


    01

    进阶操作:将需要插入的数据构造成一个字典,这样的做法可以让插入方法无需改动,只需要传入一个动态变化的字典就行了,改写原来的代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import pymysql

    data = {
    'id': '17110105',
    'name': 'TRH',
    'age': 20
    }
    table = 'students'
    keys = ', '.join(data.keys())
    values = ', '.join(['%s']*len(data))
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO {table}({keys}) VALUES ({values})'.format(table=table, keys=keys, values=values)
    try:
    cursor.execute(sql, tuple(data.values()))
    print('数据插入成功!')
    db.commit()
    except:
    print('数据插入失败!')
    db.rollback()
    db.close()

    传入的数是字典,将其定义为 data 变量,表名定义成变量 table,构造插入的字段 id、name 和 age。', '.join(data.keys()) 的结果就是 id, name, age,接着需要构造多个 %s 当作占位符,有三个字段,就需要构造 %s, %s, %s。首先定义长度为 1 的数组 ['%s'],然后用乘法将其扩充为 ['%s', '%s', '%s'],再调用 join() 方法,最终变成 %s, %s, %s。再利用字符串的 format() 方法将表名、字段名和占位符构造出来。最终的 SQL 语句就被动态构造成了如下语句:

    1
    INSERT INTO students(id, name, age) VALUES (%s, %s, %s)

    【10.5】更新数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import pymysql

    data = {
    'id': '17110105',
    'name': 'TRH',
    'age': 21
    }
    table = 'students'
    keys = ', '.join(data.keys())
    values = ', '.join(['%s']*len(data))
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'INSERT INTO {table}({keys}) VALUES ({values}) ON DUPLICATE KEY UPDATE'.format(table=table, keys=keys, values=values)
    update = ','.join(["{key} = % s".format(key=key) for key in data])
    sql += update
    try:
    if cursor.execute(sql, tuple(data.values())*2):
    print('数据插入成功!')
    db.commit()
    except:
    print('数据插入失败!')
    db.rollback()
    db.close()

    ON DUPLICATE KEY UPDATE 表示如果主键已经存在,就执行更新操作,最终被构造成如下语句:

    1
    INSERT INTO students(id, name, age) VALUES (% s, % s, % s) ON DUPLICATE KEY UPDATE id = % s, name = % s, age = % s

    【10.6】删除数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import pymysql

    table = 'students'
    condition = 'age = 20'
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'DELETE FROM {table} WHERE {condition}'.format(table=table, condition=condition)
    try:
    cursor.execute(sql)
    print('数据删除成功!')
    db.commit()
    except:
    print('数据删除失败!')
    db.rollback()
    db.close()

    删除操作直接使用 DELETE 语句,指定要删除的目标表名和删除条件即可

    【10.7】查询数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import pymysql

    table = 'students'
    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='spiders')
    cursor = db.cursor()
    sql = 'SELECT * FROM students WHERE age >= 20'
    try:
    cursor.execute(sql)
    print('Count:', cursor.rowcount)
    one = cursor.fetchone()
    print('One:', one)
    results = cursor.fetchall()
    print('Results:', results)
    print('Results Type:', type(results))
    for row in results:
    print(row)
    except:
    print('查询失败!')

    sql = 'SELECT * FROM students WHERE age >= 20':构造一条 SQL 语句,将年龄 大于等于20 岁的学生查询出来

    cursor.rowcount:调用 cursor 的 rowcount 属性获取查询结果的条数

    cursor.fetchone():调用 cursor 的 fetchone() 方法,获取结果的第一条数据,返回结果是元组形式,元组的元素顺序跟字段一一对应,即第一个元素就是第一个字段 id,第二个元素就是第二个字段 name,以此类推

    cursor.fetchall():调用 cursor 的 fetchall() 方法,得到结果的所有数据,它是二重元组,每个元素都是一条记录,本例中显示的是 3 条数据而不是 4 条,这是因为它的内部实现有一个偏移指针用来指向查询结果,最开始偏移指针指向第一条数据,取一次之后,指针偏移到下一条数据,这样再取的话,就会取到下一条数据了。我们最初调用了一次 fetchone 方法,这样结果的偏移指针就指向下一条数据,fetchall 方法返回的是偏移指针指向的数据一直到结束的所有数据,所以该方法获取的结果就只剩 3 个了

    【10.8】实战训练 — 爬取CSDN博客标题和地址保存到 MySQL

    利用 requests 库构建请求,BeautifulSoup 解析库解析网页,获取自己博客文章的标题和地址,将其储存到本地 MySQL 数据库中,事先已经创建好了一个 blog 数据库,并创建了一个名为 article 的数据表,数据表包含 id、title、url 三个字段,其中 id 的 AUTO_INCREMENT 属性可以使 id 自己增加,PRIMARY KEY 关键字用于将 id 定义为主键

    创建 article 数据表:

    1
    2
    3
    4
    5
    6
    7
    import pymysql

    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')
    cursor = db.cursor()
    sql = 'CREATE TABLE IF NOT EXISTS article (id INT NOT NULL AUTO_INCREMENT, title VARCHAR(255) NOT NULL, url VARCHAR(255) NOT NULL, PRIMARY KEY (id))'
    cursor.execute(sql)
    db.close()

    获取文章标题和对应的 URL 并将其储存到 MySQL 中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import requests
    import pymysql
    from bs4 import BeautifulSoup

    db = pymysql.connect(host='localhost', user='root', password='000000', port=3306, db='blog')
    cursor = db.cursor()

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    }
    url = "https://blog.csdn.net/qq_36759224"

    request = requests.get(url, headers=headers)
    soup = BeautifulSoup(request.text, 'lxml')
    title_list = soup.find_all('h4')
    for list in title_list:
    s = list.a.text.strip()
    title = s.replace('原', '')
    url = list.a['href'].strip()
    # print(title + url)
    cursor.execute('INSERT INTO article (title, url) VALUES (%s, %s)', (title, url))
    db.commit()
    print('数据写入完毕!')
    db.close()

    在命令行中使用 SELECT * FROM article; 命令可以查看到数据已经成功获取并储存到了数据库中:


    02
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - MySQL - - - -
    - - - - - Python3 爬虫学习笔记 C09 - - /2019/08/27/A39-Python3-spider-C09/ - -
    Python3 爬虫学习笔记第九章 —— 【文件储存】

    用解析器解析出数据之后,还需要对数据进行保存。保存的形式多种多样,最简单的形式是直接保存为文本文件,如 TXT、JSON、CSV 等。

    【9.1】TXT 文本存储

    TXT 文本存储的优点:操作非常简单,TXT 文本几乎兼容任何平台;缺点:不利于检索。

    【9.1.1】基本示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import requests
    from lxml import etree

    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    }
    url = "https://blog.csdn.net/qq_36759224"

    request = requests.get(url, headers=headers)
    tree = etree.HTML(request.text)
    title_list = tree.xpath('//h4/a/text()')
    for title in title_list:
    with open('blog.txt', 'a', encoding='utf8') as fp:
    fp.write(title)

    代码实现了我的 CSDN 博客首页所有博文标题的爬取,利用 requests 请求库发送请求,获取响应,用 XPath 获取每一篇博文的标题,然后写入 blog.txt 文件中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    帝都的凛冬
    最新屏蔽 CSDN 广告方法,专注阅读学习!
    使用Github Pages和Hexo搭建自己的独立博客【超级详细的小白教程】
    Python3 爬虫学习笔记 C08【解析库 Beautiful Soup】
    Python3 爬虫学习笔记 C07 【解析库 lxml】
    Python3 爬虫学习笔记 C06 【正则表达式】
    Python3 爬虫学习笔记 C05 【Selenium + 无界面浏览器】
    Python3 已经安装相关库,Pycharm 仍然报错 ModuleNotFoundError: No module named 'xxxxxx' 的解决办法
    Windows/Android/iOS 等常见 User-Agent 大全
    Selenium 显式等待条件及其含义
    Python3 爬虫学习笔记 C04 【自动化测试工具 Selenium】
    Python3 爬虫学习笔记 C03 【Ajax 数据爬取】
    Python3 爬虫学习笔记 C02 【基本库 requests 的使用】
    Python3 爬虫学习笔记 C01 【基本库 urllib 的使用】
    利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS
    光学字符识别 Tesseract-OCR 的下载、安装和基本用法
    Github+jsDelivr+PicGo 打造稳定快速、高效免费图床
    利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持
    Python 中 if __name__ == '__main__': 的理解
    Hexo 博客本地预览报错:Error: listen EADDRINUSE 0.0.0.0:4000
    谷歌浏览器检查更新时出错:无法启动更新检查(错误代码为 3: 0x80080005 -- system level)

    【9.1.2】打开方式

    open() 方法的第二个参数为打开方式,不同的打开方式如下:

    读写方式可否读写若文件不存在写入方式
    r读取报错不可写入
    rb以二进制方式读取报错不可写入
    r+读取 + 写入报错覆盖写入
    rb+以二进制方式读取+写入报错覆盖写入
    w写入创建覆盖写入
    wb以二进制方式写入创建覆盖写入
    w+读取 + 写入创建覆盖写入
    wb+以二进制方式读取+写入创建覆盖写入
    a写入创建附加写入
    ab以二进制方式写入创建附加写入
    a+读取 + 写入创建附加写入
    ab+以二进制方式读取+写入创建附加写入

    【9.2】JSON 文件存储

    JSON,全称为 JavaScript Object Notation, 即 JavaScript 对象标记,它通过对象和数组的组合来表示数据,构造简洁但是结构化程度非常高,是一种轻量级的数据交换格式。

    【9.2.1】对象和数组

    在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等,但是对象和数组是比较特殊且常用的两种类型

    • 对象:它在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2, …} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。

    • 数组:数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 [“java”, “javascript”, “vb”, …] 的索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引用得多。同样,值的类型可以是任意类型。

    示例:一个 JSON 对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    [{
    "name": "TRH",
    "gender": "male",
    "birthday": "1999-01-25"
    }, {
    "name": "XXX",
    "gender": "female",
    "birthday": "1999-10-18"
    }]

    【9.2.2】读取 JSON

    Python 里面的 JSON 库可以实现对 JSON 文件的读写操作,调用 JSON 库的 loads 方法将 JSON 文本字符串转为 JSON 对象、 dumps() 方法将 JSON 对象转为文本字符串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import json

    str = '''
    [{
    "name": "TRH",
    "gender": "male",
    "birthday": "1999-01-25"
    }, {
    "name": "XXX",
    "gender": "female",
    "birthday": "1999-10-18"
    }]
    '''
    data = json.loads(str)
    print(data)
    print(data[0]['name'])
    print(data[0].get('name'))
    print(data[0].get('age'))
    print(data[0].get('age', 25))

    使用 loads 方法将字符串转为 JSON 对象,通过索引来获取对应的内容,获取键值时有两种方式,一种是中括号加键名,另一种是通过 get 方法传入键名。使用 get 方法,如果键名不存在,则不会报错,会返回 None,get 方法还可以传入第二个参数(即默认值),尝试获取一个原字典中不存在的键名,此时默认会返回 None。如果传入第二个参数(即默认值),那么在不存在的情况下返回该默认值。

    1
    2
    3
    4
    5
    [{'name': 'TRH', 'gender': 'male', 'birthday': '1999-01-25'}, {'name': 'XXX', 'gender': 'female', 'birthday': '1999-10-18'}]
    TRH
    TRH
    None
    25

    【9.2.3】写入 JSON 文件

    调用 dumps 方法可以将 JSON 对象转化为字符串,然后再调用文件的 write 方法即可写入文本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import json

    data = [{
    'name': 'TRH',
    'gender': 'male',
    'birthday': '1999-01-25'
    }]
    with open('data.json', 'w') as fp:
    fp.write(json.dumps(data))

    data.json 文件:

    1
    [{"name": "TRH", "gender": "male", "birthday": "1999-01-25"}]

    添加参数 indent(代表缩进字符个数),将会格式化输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import json

    data = [{
    'name': 'TRH',
    'gender': 'male',
    'birthday': '1999-01-25'
    }]
    with open('data.json', 'w') as file:
    file.write(json.dumps(data, indent=2))

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    [
    {
    "name": "TRH",
    "gender": "male",
    "birthday": "1999-01-25"
    }
    ]

    如果 JSON 中包含中文字符,需要指定参数 ensure_ascii 为 False,另外还要规定文件输出的编码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import json

    data = [{
    'name': '小明',
    'gender': '男',
    'birthday': '1999年01月25日'
    }]
    with open('data.json', 'w', encoding='utf-8') as file:
    file.write(json.dumps(data, indent=2, ensure_ascii=False))

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    [
    {
    "name": "小明",
    "gender": "男",
    "birthday": "1999年01月25日"
    }
    ]

    【9.3】CSV 文本存储

    CSV(Comma-Separated Values)是逗号分隔值或字符分隔值的文件格式,其文件以纯文本的形式储存表格数据(数字和文本),CSV 文件的行与行之间用换行符分隔,列与列之间用逗号分隔

    【9.3.1】写入

    1
    2
    3
    4
    5
    6
    7
    8
    import csv

    with open('data.csv', 'w') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['id', 'name', 'age'])
    writer.writerow(['10001', 'TRHX', 20])
    writer.writerow(['10002', 'Bob', 22])
    writer.writerow(['10003', 'Jordan', 21])

    打开 data.csv 文件,调用 CSV 库的 writer 方法初始化写入对象,然后调用 writerow 方法传入每行的数据即可完成写入,用 Excel 打开 data.csv 文件将是表格形式

    1
    2
    3
    4
    5
    6
    7
    id,name,age

    10001,TRHX,20

    10002,Bob,22

    10003,Jordan,21

    默认每一行之间是有一行空格的,可以使用参数 newline 来去除空行:

    1
    2
    3
    4
    5
    6
    7
    8
    import csv

    with open('data.csv', 'w', newline='') as csvfile:
    writer = csv.writer(csvfile)
    writer.writerow(['id', 'name', 'age'])
    writer.writerow(['10001', 'TRHX', 20])
    writer.writerow(['10002', 'Bob', 22])
    writer.writerow(['10003', 'Jordan', 21])

    输出结果:

    1
    2
    3
    4
    id,name,age
    10001,TRHX,20
    10002,Bob,22
    10003,Jordan,21

    列与列之间的分隔符是可以修改的,只需要传入 delimiter 参数即可:

    1
    2
    3
    4
    5
    6
    7
    8
    import csv

    with open('data.csv', 'w') as csvfile:
    writer = csv.writer(csvfile, delimiter=' ')
    writer.writerow(['id', 'name', 'age'])
    writer.writerow(['10001', 'TRHX', 20])
    writer.writerow(['10002', 'Bob', 22])
    writer.writerow(['10003', 'Jordan', 21])

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    id name age

    10001 TRHX 20

    10002 Bob 22

    10003 Jordan 21

    调用 writerows 方法也可以同时写入多行,此时参数就需要为二维列表:

    1
    2
    3
    4
    5
    6
    import csv

    with open('data.csv', 'w') as csvfile:
    writer = csv.writer(csvfile, delimiter=' ')
    writer.writerow(['id', 'name', 'age'])
    writer.writerows([['10001', 'TRHX', 20], ['10002', 'Bob', 22], ['10003', 'Jordan', 21]])

    输出结果仍与原来的一样

    此外 CSV 库中也提供了字典的写入方式:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import csv

    with open('data.csv', 'w') as csvfile:
    fieldnames = ['id', 'name', 'age']
    writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow({'id': '10001', 'name': 'TRHX', 'age': 20})
    writer.writerow({'id': '10002', 'name': 'Bob', 'age': 22})
    writer.writerow({'id': '10003', 'name': 'Jordan', 'age': 21})

    首先定义 3 个字段,用 fieldnames 表示,然后将其传给 DictWriter 来初始化一个字典写入对象,接着可以调用 writeheader 方法先写入头信息,然后再调用 writerow 方法传入相应字典即可

    1
    2
    3
    4
    5
    6
    7
    id,name,age

    10001,TRHX,20

    10002,Bob,22

    10003,Jordan,21

    【9.3.2】读取

    有写入方法,同样也可以使用 csv 库来读取 CSV 文件:

    1
    2
    3
    4
    5
    6
    import csv

    with open('data.csv', 'r', encoding='utf-8') as csvfile:
    reader = csv.reader(csvfile)
    for row in reader:
    print(row)

    构造 Reader 对象,遍历输出每行的内容,每一行都是一个列表形式。(如果 CSV 文件中包含中文的话,还需要指定文件编码)读取结果:

    1
    2
    3
    4
    ['id', 'name', 'age']
    ['10001', 'TRHX', '20']
    ['10002', 'Bob', '22']
    ['10003', 'Jordan', '21']

    此外,还可以利用 pandas 的 read_csv 方法将数据从 CSV 中读取出来(pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。Pandas 纳入了大量库和一些标准的数据模型,提供了高效地操作大型数据集所需的工具)

    1
    2
    3
    4
    import pandas as pd

    df = pd.read_csv('data.csv')
    print(df)

    读取结果:

    1
    2
    3
    4
          id    name  age
    0 10001 TRHX 20
    1 10002 Bob 22
    2 10003 Jordan 21
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 文件储存 - - - -
    - - - - - Python3 爬虫学习笔记 C08 - - /2019/08/26/A38-Python3-spider-C08/ - -
    Python3 爬虫学习笔记第八章 —— 【解析库 Beautiful Soup】

    【8.1】关于 Beautiful Soup

    Beautiful Soup 可以从 HTML 或者 XML 文件中提取数据,Beautiful Soup 可以提供一些简单的、Python 式的函数用来处理导航、搜索、修改分析树等,它借助网页的结构和属性等特性来解析网页,lxml 只会局部遍历,而 Beautiful Soup 是基于 HTML DOM 的,会载入整个文档,解析整个 DOM 树,因此时间和内存开销都会大很多,所以性能要低于lxml

    抓取工具速度使用难度安装难度
    正则最快困难无(内置)
    lxml简单一般
    BeautifulSoup最简单简单

    【8.2】Beautiful Soup 的基本使用

    需要使用命令 pip install bs4 安装库,Beautiful Soup 在解析时依赖解析器,除了支持 Python 标准库中的 HTML 解析器外,还支持一些第三方解析器:

    解析器使用方法优势劣势
    Python 标准库BeautifulSoup(markup, “html.parser”)Python 的内置标准库、执行速度适中 、文档容错能力强Python 2.7.3 or 3.2.2) 前的版本中文容错能力差
    LXML HTML 解析器BeautifulSoup(markup, “lxml”)速度快、文档容错能力强需要安装 C 语言库
    LXML XML 解析器BeautifulSoup(markup, “xml”)速度快、唯一支持 XML 的解析器需要安装 C 语言库
    html5libBeautifulSoup(markup, “html5lib”)最好的容错性、以浏览器的方式解析文档、生成 HTML5 格式的文档速度慢、不依赖外部扩展

    基本使用:

    1
    2
    3
    4
    from bs4 import BeautifulSoup
    soup = BeautifulSoup('<p>Hello</p>', 'lxml')
    # soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')
    print(soup.p.string)

    输出结果:

    1
    Hello

    【8.3】节点选择器

    直接调用节点的名称就可以选择节点元素,再调用 string 属性就可以得到节点内的文本

    【8.3.1】元素选择

    新建 soup.html 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>测试bs4</title>
    </head>
    <body>
    <div>
    甄姬
    <p>百里守约</p>
    <p>李白</p>
    太乙真人
    </div>
    <div class="song">
    <p>李清照</p>
    <p>王安石</p>
    <p>苏轼</p>
    <p>柳宗元</p>
    <a href="http://www.song.com/" title="赵匡义" target="_self">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>
    <img src="http://www.baidu.com/meinv.jpg" alt="">
    <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
    </div>
    <div class="tang">
    <ul>
    <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>
    <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>
    <li><a href="http://www.126.com" alt="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>
    <li><a href="http://www.sina.com" class="du">杜甫</a> </li>
    <li><b>唐朝</b></li>
    <li><i>宋朝</i></li>
    <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>
    </ul>
    </div>

    </body>
    </html>

    1
    2
    3
    4
    5
    6
    7
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')
    print(soup.title)
    print(type(soup.title))
    print(soup.title.string)
    print(soup.head)
    print(soup.p)

    依次查找 title、head、p 节点。输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    <title>测试bs4</title>
    <class 'bs4.element.Tag'>
    测试bs4
    <head>
    <meta charset="utf-8"/>
    <title>测试bs4</title>
    </head>
    <p>百里守约</p>

    【8.3.2】提取信息

    • string 属性:获取节点包含的文本值(如果标签里面还有标签,那么string获取到的结果为None)
    • text 属性:获取节点包含的文本值
    • get_text() 属性:获取节点包含的文本值
    • name 属性:获取节点的名称
    • attrs :获取所有属性
    • attrs[‘属性名’] :获取指定属性

    依然以 soup.html 为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')
    print(soup.title)
    print(soup.title.text)
    print(soup.title.get_text())
    print(soup.title.string)
    print(soup.div.string)
    print(soup.div.text)
    print(soup.title.name)
    print(soup.a['href']) # 获取href属性
    print(soup.a['title']) # 获取title属性
    print(soup.a['target']) # 获取target属性
    print(soup.a.attrs) # 获取所有属性
    print(soup.a.attrs['href']) # 获取href属性

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <title>测试bs4</title>
    测试bs4
    测试bs4
    测试bs4
    None

    甄姬
    百里守约
    李白
    太乙真人

    title
    http://www.song.com/
    赵匡义
    _self
    {'href': 'http://www.song.com/', 'title': '赵匡义', 'target': '_self'}
    http://www.song.com/

    【8.3.3】嵌套选择

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from bs4 import BeautifulSoup

    html = """
    <html><head><title>This is a demo</title></head>
    <body>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(soup.head.title)
    print(type(soup.head.title))
    print(soup.head.title.string)

    获取 head 节点里面的 title 节点,输出结果:

    1
    2
    3
    <title>This is a demo</title>
    <class 'bs4.element.Tag'>
    This is a demo

    【8.3.4】关联选择

    • contents 属性:获取某个节点元素的直接子节点
    • children 属性:遍历某个节点元素的子节点
    • descendants 属性:获取某个节点元素所有的子孙节点
    • parent 属性:获取某个节点元素的父节点
    • parents 属性:获取某个节点元素所有的祖先节点
    • next_sibling 属性:获取节点的下一个兄弟元素
    • previous_sibling 属性:获取节点的上一个兄弟元素
    • next_siblings 属性:获取某个节点所有后面的兄弟元素
    • previous_siblings 属性:获取某个节点所有前面的兄弟元素
    contents 属性应用示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from bs4 import BeautifulSoup

    html = """
    <html>
    <head>
    <title>The Dormouse's story</title>
    </head>
    <body>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
    and they lived at the bottom of a well.
    </p>
    <p class="story">...</p>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(soup.p.contents)

    获取 p 节点元素的直接子节点,输出结果:

    1
    2
    3
    ['\n            Once upon a time there were three little sisters; and their names were\n            ', <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>, '\n', <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, ' \n and\n ', <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>, '\n and they lived at the bottom of a well.\n ']

    children 属性应用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from bs4 import BeautifulSoup

    html = """
    <html>
    <head>
    <title>The Dormouse's story</title>
    </head>
    <body>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
    and they lived at the bottom of a well.
    </p>
    <p class="story">...</p>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(soup.p.children)
    for i, child in enumerate(soup.p.children):
    print(i, child)

    遍历 p 节点元素的子节点,输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <list_iterator object at 0x00000228E3C205F8>
    0
    Once upon a time there were three little sisters; and their names were

    1 <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    2

    3 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
    4
    and

    5 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
    6
    and they lived at the bottom of a well.

    descendants 属性应用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from bs4 import BeautifulSoup

    html = """
    <html>
    <head>
    <title>The Dormouse's story</title>
    </head>
    <body>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
    and they lived at the bottom of a well.
    </p>
    <p class="story">...</p>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(soup.p.descendants)
    for i, child in enumerate(soup.p.descendants):
    print(i, child)


    获取 p 节点元素所有的子孙节点,输出结果:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <generator object descendants at 0x0000018404A4C3B8>
    0
    Once upon a time there were three little sisters; and their names were

    1 <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    2

    3 <span>Elsie</span>
    4 Elsie
    5

    6

    7 <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>
    8 Lacie
    9
    and

    10 <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>
    11 Tillie
    12
    and they lived at the bottom of a well.


    parent 属性应用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    from bs4 import BeautifulSoup
    html = """
    <html>
    <head>
    <title>The Dormouse's story</title>
    </head>
    <body>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    </p>
    </body>
    </html>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(soup.a.parent)


    获取 a 节点元素的父节点,输出结果:
    1
    2
    3
    4
    5
    6
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    </p>


    parents 属性应用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    from bs4 import BeautifulSoup

    html = """
    <html>
    <body>
    <p class="story">
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    </p>
    """
    soup = BeautifulSoup(html, 'lxml')
    print(type(soup.a.parents))
    print(list(enumerate(soup.a.parents)))


    获取 a 节点元素所有的祖先节点,输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <class 'generator'>
    [(0, <p class="story">
    <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    </p>), (1, <body>
    <p class="story">
    <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    </p>
    </body>), (2, <html>
    <body>
    <p class="story">
    <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    </p>
    </body></html>), (3, <html>
    <body>
    <p class="story">
    <a class="sister" href="http://example.com/elsie" id="link1">
    <span>Elsie</span>
    </a>
    </p>
    </body></html>)]


    next_sibling、previous_sibling、next_siblings、previous_siblings 属性应用示例:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    html = """
    <html>
    <body>
    <p class="story">
    Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">
    <span>Elsie</span>
    </a>
    Hello
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a>
    and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>
    and they lived at the bottom of a well.
    </p>
    </body>
    </html>
    """
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'lxml')
    print('Next Sibling', soup.a.next_sibling)
    print('Prev Sibling', soup.a.previous_sibling)
    print('Next Siblings', list(enumerate(soup.a.next_siblings)))
    print('Prev Siblings', list(enumerate(soup.a.previous_siblings)))

    next_sibling 和 previous_sibling 分别获取 a 节点的下一个和上一个兄弟元素,next_siblings 和 previous_siblings 则分别返回 a 节点后面和前面的兄弟节点,输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    Next Sibling 
    Hello

    Prev Sibling
    Once upon a time there were three little sisters; and their names were

    Next Siblings [(0, '\n Hello\n '), (1, <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>), (2, ' \n and\n '), (3, <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>), (4, '\n and they lived at the bottom of a well.\n ')]
    Prev Siblings [(0, '\n Once upon a time there were three little sisters; and their names were\n ')]

    【8.4】方法选择器

    节点选择器直接调用节点的名称就可以选择节点元素,如果进行比较复杂的选择的话,方法选择器是一个不错的选择,它更灵活,常见的方法有 find_all、find 等,调用它们,直接传入相应的参数,就可以灵活查询了。

    【8.4.1】find_all() 方法

    find_all 方法可以查询所有符合条件的元素,给它传入一些属性或文本来得到符合条件的元素。find_all 方法的 API:find_all(name , attrs , recursive , text , **kwargs)
    新建 soup.html:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>测试bs4</title>
    </head>
    <body>
    <div>
    甄姬
    <p>百里守约</p>
    <p>李白</p>
    太乙真人
    </div>
    <div class="song">
    <p>李清照</p>
    <p>王安石</p>
    <p>苏轼</p>
    <p>柳宗元</p>
    <a href="http://www.song.com/" title="赵匡义" target="_self">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>
    <img src="http://www.baidu.com/meinv.jpg" alt="">
    <a href="" class="du">总为浮云能蔽日,长安不见使人愁</a>
    </div>
    <div class="tang">
    <ul>
    <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>
    <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>
    <li><a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>
    <li><a href="http://www.sina.com" class="du">杜甫</a> </li>
    <li><b>唐朝</b></li>
    <li><i>宋朝</i></li>
    <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>
    </ul>
    </div>

    </body>
    </html>

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from bs4 import BeautifulSoup

    soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')
    print(soup.find_all('a'), '\n')
    print(soup.find_all('a')[1], '\n')
    print(soup.find_all('a')[1].text, '\n')
    print(soup.find_all(['a', 'b', 'i']), '\n')
    print(soup.find_all('a', limit=2), '\n')
    print(soup.find_all(title='qing'), '\n')
    print(soup.find_all(attrs={'id': 'feng'}), '\n')

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    [<a href="http://www.song.com/" target="_self" title="赵匡义">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class="du" href="">总为浮云能蔽日,长安不见使人愁</a>, <a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class="du" href="http://www.sina.com">杜甫</a>, <a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>] 

    <a class="du" href="">总为浮云能蔽日,长安不见使人愁</a>

    总为浮云能蔽日,长安不见使人愁

    [<a href="http://www.song.com/" target="_self" title="赵匡义">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class="du" href="">总为浮云能蔽日,长安不见使人愁</a>, <a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>, <a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a>, <a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>, <a class="du" href="http://www.sina.com">杜甫</a>, <b>唐朝</b>, <i>宋朝</i>, <a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>]

    [<a href="http://www.song.com/" target="_self" title="赵匡义">宋朝是最强大的王朝,不是军队的强大,而是经济很强大,国民都很有钱。</a>, <a class="du" href="">总为浮云能蔽日,长安不见使人愁</a>]

    [<a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a>]

    [<a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a>]

    【8.4.2】find() 方法

    find() 方法使用方法与 find_all() 方法相同,不同的是,find 方法返回的是单个元素,也就是第一个匹配的元素,而 find_all 返回的是所有匹配的元素组成的列表
    特别的:

    • find_parents 和 find_parent:前者返回所有祖先节点,后者返回直接父节点。

    • find_next_siblings 和 find_next_sibling:前者返回后面所有的兄弟节点,后者返回后面第一个兄弟节点。

    • find_previous_siblings 和 find_previous_sibling:前者返回前面所有的兄弟节点,后者返回前面第一个兄弟节点。

    • find_all_next 和 find_next:前者返回节点后所有符合条件的节点,后者返回第一个符合条件的节点。

    • find_all_previous 和 find_previous:前者返回节点前所有符合条件的节点,后者返回第一个符合条件的节点。

    【8.5】CSS 选择器

    使用 CSS 选择器,只需要调用 select 方法,传入相应的 CSS 选择器即可
    新建 soup.html 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <!DOCTYPE html>
    <html lang="en">
    <div class="tang">
    <ul>
    <li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>
    <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>
    <li><a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>
    <li><a href="http://www.sina.com" class="du">杜甫</a> </li>
    <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>
    </ul>
    </div>

    </body>
    </html>

    通过 CSS 选择器依次选择 class=”tang” 的 div 节点下的 a 节点、id 为 feng 的节点以及其 href 元素:

    1
    2
    3
    4
    5
    6
    7
    from bs4 import BeautifulSoup

    soup = BeautifulSoup(open('soup.html', encoding='utf8'), 'lxml')
    print(soup.select('li'), '\n')
    print(soup.select('.tang > ul > li > a')[2], '\n')
    print(soup.select('#feng')[0].text, '\n')
    print(soup.select('#feng')[0]['href'], '\n')

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    [<li><a href="http://www.baidu.com" title="qing">清明时节雨纷纷,路上行人欲断魂,借问酒家何处有,牧童遥指杏花村。</a> </li>, <li><a href="http://www.163.com" title="qin">秦时明月汉时关,万里长征人未还,但使龙城飞将在,不教胡马度阴山。</a> </li>, <li><a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a> </li>, <li><a class="du" href="http://www.sina.com">杜甫</a> </li>, <li><a href="http://www.haha.com" id="feng">凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。</a> </li>] 

    <a href="http://www.126.com" name="qi">岐王宅里寻常见,崔九堂前几度闻,正是江南好风景,落花时节又逢君。</a>

    凤凰台上凤凰游,凤去台空江自流,吴宫花草埋幽径,晋代衣冠成古丘。

    http://www.haha.com

    附表:CSS 选择器,来源:https://www.w3school.com.cn/cssref/css_selectors.asp

    选择器例子例子描述CSS
    .class.intro选择 class=”intro” 的所有元素1
    #id#firstname选择 id=”firstname” 的所有元素1
    **选择所有元素2
    elementp选择所有

    元素

    1
    element,elementdiv,p选择所有
    元素和所有

    元素

    1
    element elementdiv p选择
    元素内部的所有

    元素

    1
    element>elementdiv>p选择父元素为
    元素的所有

    元素

    2
    element+elementdiv+p选择紧接在
    元素之后的所有

    元素

    2
    [attribute][target]选择带有 target 属性所有元素2
    [attribute=value][target=_blank]选择 target=”_blank” 的所有元素2
    [attribute~=value][title~=flower]选择 title 属性包含单词 “flower” 的所有元素2
    [attribute=value][lang=en]选择 lang 属性值以 “en” 开头的所有元素2
    :linka:link选择所有未被访问的链接1
    :visiteda:visited选择所有已被访问的链接1
    :activea:active选择活动链接1
    :hovera:hover选择鼠标指针位于其上的链接1
    :focusinput:focus选择获得焦点的 input 元素2
    :first-letterp:first-letter选择每个

    元素的首字母

    1
    :first-linep:first-line选择每个

    元素的首行

    1
    :first-childp:first-child选择属于父元素的第一个子元素的每个

    元素

    2
    :beforep:before在每个

    元素的内容之前插入内容

    2
    :afterp:after在每个

    元素的内容之后插入内容

    2
    :lang(language)p:lang(it)选择带有以 “it” 开头的 lang 属性值的每个

    元素

    2
    element1~element2p~ul选择前面有

    元素的每个

      元素

    3
    [attribute^=value]a[src^=”https”]选择其 src 属性值以 “https” 开头的每个 元素3
    [attribute$=value]a[src$=”.pdf”]选择其 src 属性以 “.pdf” 结尾的所有 元素3
    [attribute*=value]a[src*=”abc”]选择其 src 属性中包含 “abc” 子串的每个 元素3
    :first-of-typep:first-of-type选择属于其父元素的首个

    元素的每个

    元素

    3
    :last-of-typep:last-of-type选择属于其父元素的最后

    元素的每个

    元素

    3
    :only-of-typep:only-of-type选择属于其父元素唯一的

    元素的每个

    元素

    3
    :only-childp:only-child选择属于其父元素的唯一子元素的每个

    元素

    3
    :nth-child(n)p:nth-child(2)选择属于其父元素的第二个子元素的每个

    元素

    3
    :nth-last-child(n)p:nth-last-child(2)同上,从最后一个子元素开始计数3
    :nth-of-type(n)p:nth-of-type(2)选择属于其父元素第二个

    元素的每个

    元素

    3
    :nth-last-of-type(n)p:nth-last-of-type(2)同上,但是从最后一个子元素开始计数3
    :last-childp:last-child选择属于其父元素最后一个子元素每个

    元素

    3
    :root:root选择文档的根元素3
    :emptyp:empty选择没有子元素的每个

    元素(包括文本节点)

    3
    :target#news:target选择当前活动的 #news 元素3
    :enabledinput:enabled选择每个启用的 元素3
    :disabledinput:disabled选择每个禁用的 元素3
    :checkedinput:checked选择每个被选中的 元素3
    :not(selector):not(p)选择非

    元素的每个元素

    3
    ::selection::selection选择被用户选取的元素部分3

    【8.6】附表:Beautiful Soup 库 soup 对象常用属性与方法

    基本元素说明返回类型
    tagsoup.abs4.element.Tag
    namesoup.a.namestr
    attrssoup.a.attrsdict
    contents子节点list
    children遍历子节点list_iterator
    descendants遍历所有子孙节点generator
    parent返回父亲标签bs4.element.Tag
    parents上行遍历父辈标签generator
    prettify()添加/nstr
    find_all(name,attr)soup.find_all(‘a’)/([‘a’,‘b’])/(True)/(‘p’,‘course’)/(id=‘link1’)/(string=‘python’)bs4.element.ResultSet
    find()soup.find(‘a’)/返回第一个a标签bs4.element.Tag
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - Beautiful Soup - - - -
    - - - - - Python3 爬虫学习笔记 C07 - - /2019/08/25/A37-Python3-spider-C07/ - -
    Python3 爬虫学习笔记第七章 —— 【解析库 lxml】

    【7.1】关于 lxml

    lxml 是 Python 的一个解析库,支持 HTML 和 XML 的解析,支持 XPath 解析方式,解析效率非常高,使用前需要用命令 pip3 install lxml 安装 lxml 库

    【7.2】使用 XPath

    XPath(XML Path Language)即 XML 路径语言, lxml 解析库使用的正是 XPath 语法,最初是用来搜寻 XML 文档的,是一门在 XML 文档中查找信息的语言,它同样适用于 HTML 文档的搜索

    XPath 常用规则

    表达式描述
    nodename选取此节点的所有子节点
    /从当前节点选取直接子节点
    //从当前节点选取子孙节点
    .选取当前节点
    ..选取当前节点的父节点
    @选取属性
    *通配符,选择所有元素节点与元素名
    @*选取所有属性
    [@attrib]选取具有给定属性的所有元素
    [@attrib=’value’]选取给定属性具有给定值的所有元素
    [tag]选取所有具有指定元素的直接子节点
    [tag=’text’]选取所有具有指定元素并且文本内容是text节点

    浏览器插件 XPath Helper,在线验证 XPath,谷歌商店下载地址:https://chrome.google.com/webstore/detail/hgimnogjllphhhkhlmebbmlgjoejdpjl

    XPath 基本使用方法:首先使用代码 from lxml import etree导入库,然后将 HTML 文档变成一个对象,再调用对象的方法去查找指定的节点,方法有两种:tree = etree.parse() 为本地文件查找,tree = etree.HTML() 为网络文件查找,再使用语句 tree.xpath() 查找指定节点。

    【7.3】查找所有节点

    新建一个 xpath.html 本地文件,内容如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
     <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>xpath测试</title>
    </head>
    <body>
    <div class="song">
    火药
    <b>指南针</b>
    <b>印刷术</b>
    造纸术
    </div>
    <div class="tang">
    <ul>
    <li class="balove">停车坐爱枫林晚,霜叶红于二月花。</li>
    <li id="hua">商女不知亡国恨,隔江犹唱后庭花。</li>
    <li class="love" name="yang">一骑红尘妃子笑,无人知是荔枝来。</li>
    <li id="bei">葡萄美酒夜光杯,欲饮琵琶马上催。</li>
    <li><a href="http://www.baidu.com/">百度一下</a> </li>
    </ul>
    <ol>
    <li class="balucy">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li>
    <li class="lily">咋暖还寒时候,最难将息。</li>
    <li class="lilei">三杯两盏淡酒。</li>
    <li>怎敌他晚来风急。</li>
    <li>雁过也,正伤心,却是旧时相识。</li>
    <li>爱情三十六计</li>
    <li>什么是爱情</li>
    </ol>
    </div>
    </body>
    </html>

    查找所有节点:

    1
    2
    3
    4
    5
    from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//*')
    print(result)

    使用 * 代表匹配所有节点,整个 xpath.html 文件中的所有节点都会被获取到,返回形式是一个列表,每个元素是 Element 类型,其后跟了节点的名称,如 html、body、div、ul、li、a 等,所有节点都包含在列表中,输出结果如下:

    1
    [<Element html at 0x1a836a34508>, <Element head at 0x1a836a344c8>, <Element meta at 0x1a836a345c8>, <Element title at 0x1a836a34608>, <Element body at 0x1a836a34648>, <Element div at 0x1a836a346c8>, <Element b at 0x1a836a34708>, <Element b at 0x1a836a34748>, <Element div at 0x1a836a34788>, <Element ul at 0x1a836a34688>, <Element li at 0x1a836a347c8>, <Element li at 0x1a836a34808>, <Element li at 0x1a836a34848>, <Element li at 0x1a836a34888>, <Element li at 0x1a836a348c8>, <Element a at 0x1a836a34908>, <Element ol at 0x1a836a34948>, <Element li at 0x1a836a34988>, <Element li at 0x1a836a349c8>, <Element li at 0x1a836a34a08>, <Element li at 0x1a836a34a48>, <Element li at 0x1a836a34a88>, <Element li at 0x1a836a34ac8>, <Element li at 0x1a836a34b08>]

    【7.4】查找子节点

    通过 /// 即可查找元素的子节点或子孙节点:

    1
    2
    3
    4
    5
     from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ul/li')
    print(result)

    选择 ul 节点的所有直接 li 子节点:

    1
    [<Element li at 0x2a094d044c8>, <Element li at 0x2a094d045c8>, <Element li at 0x2a094d04608>, <Element li at 0x2a094d04648>, <Element li at 0x2a094d04688>]

    【7.5】查找父节点

    知道了子节点,也可以用 .. 或者 parent:: 查找其父节点

    1
    2
    3
    4
    5
     from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ol/../@class')
    print(result)

    1
    2
    3
    4
    5
    from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ol/parent::*/@class')
    print(result)

    先查找到 ol 节点,随后获取其父节点以及其 class 属性:

    1
    ['tang']

    【7.6】属性匹配

    有时候 HTML 包含多个相同名的节点,而节点的属性是不一样的,此时可以用 @ 符号进行属性过滤

    1
    2
    3
    4
    5
     from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//li[@class="balucy"]')
    print(result)

    xpath.html 文件中,只有一个 class 为 balucy 的节点:<li class="balucy">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li>,运行以上代码将返回一个该元素:

    1
    [<Element li at 0x16e53aa54c8>]

    【7.7】文本获取

    使用 text() 方法即可提取节点中的文本:

    1
    2
    3
    4
    5
     from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//li[@class="balucy"]/text()')
    print(result)

    输出结果:

    1
    ['寻寻觅觅冷冷清清,凄凄惨惨戚戚。']

    再次观察 xpath.html 文件中的 <ol></ol>这一部分:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <ol>
    <li class="balucy">寻寻觅觅冷冷清清,凄凄惨惨戚戚。</li>
    <li class="lily">咋暖还寒时候,最难将息。</li>
    <li class="lilei">三杯两盏淡酒。</li>
    <li>怎敌他晚来风急。</li>
    <li>雁过也,正伤心,却是旧时相识。</li>
    <li>爱情三十六计</li>
    <li>什么是爱情</li>
    </ol>

    如果我们想要提取 <li> 节点里面所有的文本,就可以使用 html.xpath('//ol/li/text()') 语句:

    1
    2
    3
    4
    5
    from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ol/li/text()')
    print(result)

    输出结果:

    1
    ['寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '咋暖还寒时候,最难将息。', '三杯两盏淡酒。', '怎敌他晚来风急。', '雁过也,正伤心,却是旧时相识。', '爱情三十六计', '什么是爱情']

    同样还有另一种方法,使用 html.xpath('//ol//text()') 语句,// 将会选取所有子孙节点的文本,<ol><li> 节点下的换行符也将被提取出来:

    1
    2
    3
    4
    5
    from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ol//text()')
    print(result)

    输出结果:

    1
    ['\n        ', '寻寻觅觅冷冷清清,凄凄惨惨戚戚。', '\n        ', '咋暖还寒时候,最难将息。', '\n        ', '三杯两盏淡酒。', '\n        ', '怎敌他晚来风急。', '\n        ', '雁过也,正伤心,却是旧时相识。', '\n        ', '爱情三十六计', '\n        ', '什么是爱情', '\n    ']

    【7.8】属性获取

    与属性匹配一样,属性获取仍然使用 @

    1
    2
    3
    4
    5
     from lxml import etree

    html = etree.parse('./xpath.html')
    result = html.xpath('//ul/li[5]/a/@href')
    print(result)

    获取 href 属性:

    1
    ['http://www.baidu.com/']

    【7.9】一个属性包含多个值的匹配

    某个节点的某个属性可能有多个值,例如:

    1
    <li class="li li-first"><a href="link.html">first item</a></li>

    li 节点的 class 属性有 li 和 li-first 两个值,如果使用 html.xpath('//li[@class="li"] 语句,将无法成功匹配,这时就需要使用 contains 方法了,第一个参数传入属性名称,第二个参数传入属性值,只要此属性包含所传入的属性值,就可以完成匹配了

    1
    2
    3
    4
    5
    6
    7
    from lxml import etree
    text = '''
    <li class="li li-first"><a href="link.html">first item</a></li>
    '''
    html = etree.HTML(text)
    result = html.xpath('//li[contains(@class, "li")]/a/text()')
    print(result)

    输出结果:

    1
    ['first item']

    【7.10】多个属性匹配一个节点

    XPath 还可以根据多个属性来确定一个节点,这时就需要同时匹配多个属性。此时可以使用运算符 and 来连接:

    1
    2
    3
    4
    5
    6
    7
    from lxml import etree
    text = '''
    <li class="li" name="item"><a href="link.html">first item</a></li>
    '''
    html = etree.HTML(text)
    result = html.xpath('//li[@class="li" and @name="item"]/a/text()')
    print(result)

    输出结果:

    1
    ['first item']

    示例中运用了运算符 and 来连接,此外常见的运算符如下:

    运算符描述实例返回值
    orage=19 or age=20如果 age 是 19 或者 20,则返回 true。如果 age 是其他值,则返回 false
    andage>19 and age<21如果 age 大于 19 且小于 21,则返回 true。如果 age 是其他值,则返回 false
    mod计算除法的余数5 mod 21
    |计算两个节点集//book | //cd返回所有拥有 book 和 cd 元素的节点集
    +加法10 + 515
    -减法10 - 55
    *乘法10 * 550
    div除法10 div 52
    =等于age=19如果 age 是 19,则返回 true。如果 age 不是 19,则返回 false
    !=不等于age!=19如果 age 不是 19,则返回 true。如果 age 是 19,则返回 false
    <小于age<19如果 age 小于 19,则返回 true。如果 age 不小于 19,则返回 false
    <=小于或等于age<=19如果 age 小于等于 19,则返回 true。如果 age 大于 19,则返回 false
    >大于age>19如果 age 大于 19,则返回 true。如果 age 不大于 19,则返回 false
    >=大于或等于age>=19如果 age 大于等于 19,则返回 true。如果 age 小于 19,则返回 false

    【7.11】按顺序选择节点

    某些属性可能同时匹配了多个节点,如果要选择其中几个节点,可以利用中括号传入索引的方法获取特定次序的节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    from lxml import etree

    text = '''
    <div>
    <ul>
    <li class="item-0"><a href="link1.html">first item</a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-inactive"><a href="link3.html">third item</a></li>
    <li class="item-1"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a>
    </ul>
    </div>
    '''
    html = etree.HTML(text)
    result = html.xpath('//li[1]/a/text()')
    print(result)
    result = html.xpath('//li[last()]/a/text()')
    print(result)
    result = html.xpath('//li[position()<3]/a/text()')
    print(result)
    result = html.xpath('//li[last()-2]/a/text()')
    print(result)

    • li[1]:选取第一个 li 节点;
    • li[last()]:选取最后一个 li 节点;
    • position()<3:选取位置小于 3 的 li 节点;
    • li[last()-2]:选取倒数第三个 li 节点

    输出结果:

    1
    2
    3
    4
    ['first item']
    ['fifth item']
    ['first item', 'second item']
    ['third item']

    【7.12】节点轴选择

    节点轴选择:获取子元素、兄弟元素、父元素、祖先元素等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    from lxml import etree

    text = '''
    <div>
    <ul>
    <li class="item-0"><a href="link1.html"><span>first item</span></a></li>
    <li class="item-1"><a href="link2.html">second item</a></li>
    <li class="item-inactive"><a href="link3.html">third item</a></li>
    <li class="item-1"><a href="link4.html">fourth item</a></li>
    <li class="item-0"><a href="link5.html">fifth item</a>
    </ul>
    </div>
    '''
    html = etree.HTML(text)
    result = html.xpath('//li[1]/ancestor::*')
    print(result)
    result = html.xpath('//li[1]/ancestor::div')
    print(result)
    result = html.xpath('//li[1]/attribute::*')
    print(result)
    result = html.xpath('//li[1]/child::a[@href="link1.html"]')
    print(result)
    result = html.xpath('//li[1]/descendant::span')
    print(result)
    result = html.xpath('//li[1]/following::*[2]')
    print(result)
    result = html.xpath('//li[1]/following-sibling::*')
    print(result)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    [<Element html at 0x1d3749e9548>, <Element body at 0x1d3749e94c8>, <Element div at 0x1d3749e9488>, <Element ul at 0x1d3749e9588>]
    [<Element div at 0x1d3749e9488>]
    ['item-0']
    [<Element a at 0x1d3749e9588>]
    [<Element span at 0x1d3749e9488>]
    [<Element a at 0x1d3749e9588>]
    [<Element li at 0x1d3749e94c8>, <Element li at 0x1d3749e95c8>, <Element li at 0x1d3749e9608>, <Element li at 0x1d3749e9648>]

    基本语法:轴名称::节点测试[谓语]

    轴名称对应的结果:

    轴名称结果
    ancestor选取当前节点的所有先辈(父、祖父等)
    ancestor-or-self选取当前节点的所有先辈(父、祖父等)以及当前节点本身
    attribute选取当前节点的所有属性
    child选取当前节点的所有子元素
    descendant选取当前节点的所有后代元素(子、孙等)
    descendant-or-self选取当前节点的所有后代元素(子、孙等)以及当前节点本身
    following选取文档中当前节点的结束标签之后的所有节点
    namespace选取当前节点的所有命名空间节点
    parent选取当前节点的父节点
    preceding选取文档中当前节点的开始标签之前的所有节点
    preceding-sibling选取当前节点之前的所有同级节点
    self选取当前节点

    实例:

    例子结果
    child::book选取所有属于当前节点的子元素的 book 节点
    attribute::lang选取当前节点的 lang 属性
    child::*选取当前节点的所有子元素
    attribute::*选取当前节点的所有属性
    child::text()选取当前节点的所有文本子节点
    child::node()选取当前节点的所有子节点
    descendant::book选取当前节点的所有 book 后代
    ancestor::book选择当前节点的所有 book 先辈
    ancestor-or-self::book选取当前节点的所有 book 先辈以及当前节点(如果此节点是 book 节点)
    child::*/child::price选取当前节点的所有 price 孙节点
    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - lxml - - XPath - - - -
    - - - - - Python3 爬虫学习笔记 C06 - - /2019/08/24/A36-Python3-spider-C06/ - -
    Python3 爬虫学习笔记第六章 —— 【正则表达式】

    【6.1】关于正则表达式

    正则表达式是对字符串操作的一种逻辑公式,用定义好的特定字符和这些特定字符的组合组成一个规则字符串,这个规则字符串原来表达对字符串的一种过滤逻辑,从而实现字符串的检索、替换、匹配验证等。Python 的 re 库提供了整个正则表达式的实现,包含五种方法:match、search、findall、sub、compile
    常用的匹配规则:

    模式描述
    \w匹配字母、数字及下划线
    \W匹配不是字母、数字及下划线的字符
    \s匹配任意空白字符,等价于 [\t\n\r\f]
    \S匹配任意非空字符
    \d匹配任意数字,等价于 [0-9]
    \D匹配任意非数字的字符
    \A匹配字符串开头
    \z匹配字符串结尾,如果存在换行,同时还会匹配换行符
    \Z匹配字符串结尾,如果存在换行,只匹配到换行前的结束字符串
    \G匹配最后匹配完成的位置
    \n匹配一个换行符
    \t匹配一个制表符
    ^匹配一行字符串的开头
    $匹配一行字符串的结尾
    .匹配任意字符,除了换行符,当 re.DOTALL 标记被指定时,则可以匹配包括换行符的任意字符
    […]用来表示一组字符,单独列出,比如 [amk] 匹配 a、m 或 k
    [^…]不在 [] 中的字符,比如 匹配除了 a、b、c 之外的字符
    *匹配 0 个或多个表达式
    +匹配 1 个或多个表达式
    ?匹配 0 个或 1 个前面的正则表达式定义的片段,非贪婪方式
    {n}精确匹配 n 个前面的表达式
    {n, m}匹配 n 到 m 次由前面正则表达式定义的片段,贪婪方式
    a\b匹配 a 或 b
    ( )匹配括号内的表达式,也表示一个组

    【6.2】re.match 方法

    match() 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回 None,在 match() 方法中,第一个参数传入正则表达式,第二个参数传入要匹配的字符串。

    1
    2
    3
    4
    5
    6
    7
    8
    import re

    content = 'This is a Demo_123 4567_I Love China'
    print(len(content))
    result = re.match('^This\s\w\w\s\w\s\w{5}\d{3}\s\w{6}', content)
    print(result)
    print(result.group())
    print(result.span())

    输出结果:

    1
    2
    3
    4
    36
    <_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>
    This is a Demo_123 4567_I
    (0, 25)

    打印 result 结果是 SRE_Match 对象,表明匹配成功。SRE_Match 对象有两种方法:group() 方法可以输出匹配到的内容;span() 方法可以输出匹配的范围。

    【6.2.1】提取内容

    使用括号将想提取的子字符串括起来。括号实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应每一个分组,调用 group() 方法传入分组的索引即可获取提取的结果。

    1
    2
    3
    4
    5
    6
    7
    8
    import re

    content = 'This is a Demo_123 4567_I Love China'
    result = re.match('^This\s\w\w\s\w\s(\w{5})\d{3}\s\w{6}', content)
    print(result)
    print(result.group())
    print(result.group(1))
    print(result.span())

    输出结果:

    1
    2
    3
    4
    <_sre.SRE_Match object; span=(0, 25), match='This is a Demo_123 4567_I'>
    This is a Demo_123 4567_I
    Demo_
    (0, 25)

    【6.2.2】通用匹配

    如果每个字符都用都用一个符号来匹配的话就显得比较麻烦,可以用 .*来匹配,. 可以匹配除换行符外的任意字符,* 代表匹配前面的字符无限次。

    1
    2
    3
    4
    5
    6
    7
    import re

    content = 'This is a Demo_123 4567_I Love China'
    result = re.match('^This.*China$', content)
    print(result)
    print(result.group())
    print(result.span())

    输出结果:

    1
    2
    3
    <_sre.SRE_Match object; span=(0, 36), match='This is a Demo_123 4567_I Love China'>
    This is a Demo_123 4567_I Love China
    (0, 36)

    【6.2.3】贪婪匹配

    1
    2
    3
    4
    5
    6
    7
    8
    import re

    content = 'This is a Demo_1234567_I Love China'
    result = re.match('^This.*(\d+).*China$', content)
    print(result)
    print(result.group())
    print(result.group(1))
    print(result.span())

    输出结果:

    1
    2
    3
    4
    <_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>
    This is a Demo_1234567_I Love China
    7
    (0, 35)

    .* 为贪婪匹配,会匹配尽可能多的字符,所以 \d+ 只会匹配到最后一个数字,而不是所有的数字

    【6.2.4】非贪婪匹配

    1
    2
    3
    4
    5
    6
    7
    8
    import re

    content = 'This is a Demo_1234567_I Love China'
    result = re.match('^This.*?(\d+).*China$', content)
    print(result)
    print(result.group())
    print(result.group(1))
    print(result.span())

    输出结果:

    1
    2
    3
    4
    <_sre.SRE_Match object; span=(0, 35), match='This is a Demo_1234567_I Love China'>
    This is a Demo_1234567_I Love China
    1234567
    (0, 35)

    .*? 为非贪婪匹配,会匹配尽可能少的字符,所以 \d+ 会匹配到所有的数字

    【6.2.5】转义匹配

    当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。例如 . 可以用 \. 来匹配:

    1
    2
    3
    4
    5
    6
    import re

    content = '(博客)www.itrhx.com'
    result = re.match('\(博客\)www\.itrhx\.com', content)
    print(result)
    print(result.group())

    输出结果:

    1
    2
    <_sre.SRE_Match object; span=(0, 17), match='(博客)www.itrhx.com'>
    (博客)www.itrhx.com

    【6.2.6】修饰符

    修饰符用来解决换行、大小写等问题,较为常用的有 re.S 和 re.I。

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

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import re

    content = '''This is a Demo_1234567
    _I Love China'''
    result = re.match('^This.*?(\d+).*China$', content)
    print(result)
    print(result.group())
    print(result.group(1))
    print(result.span())

    示例中 content 字段进行了换行处理,如果没有修饰符,就会报错:

    1
    2
    3
    4
    5
    Traceback (most recent call last):
    None
    File "F:/PycharmProjects/Python3爬虫/test.py", line 7, in <module>
    print(result.group())
    AttributeError: 'NoneType' object has no attribute 'group'

    添加 re.S 修饰符后即可匹配成功:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import re

    content = '''This is a Demo_1234567
    _I Love China'''
    result = re.match('^This.*?(\d+).*China$', content, re.S)
    print(result)
    print(result.group())
    print(result.group(1))
    print(result.span())

    输出结果:

    1
    2
    3
    4
    5
    <_sre.SRE_Match object; span=(0, 46), match='This is a Demo_1234567\n          _I Love China'>
    This is a Demo_1234567
    _I Love China
    1234567
    (0, 46)

    【6.3】re.search 方法

    match() 方法只能从字符串的开头开始匹配,一旦开头不匹配,那么整个匹配就失败了,match() 方法更适合用来检测某个字符串是否符合某个正则表达式的规则,而 search() 方法则会扫描整个字符串并返回第一个成功的匹配

    1
    2
    3
    4
    5
    6
    import re

    content = 'This is a Demo_1234567_I Love China'
    result = re.search('a.*?(\d{5})', content)
    print(result)
    print(result.group(1))

    输出结果:

    1
    2
    <_sre.SRE_Match object; span=(8, 20), match='a Demo_12345'>
    12345

    【6.4】re.findall 方法

    search() 方法则会扫描整个字符串,但是返回的是第一个成功的匹配,而 findall() 方法将会返回所有成功的匹配

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    import re

    html = '''<div id="songs-list">
    <h2 class="title"> 民谣 </h2>
    <p class="introduction">
    民谣歌曲列表
    </p>
    <ul id="list" class="list-group">
    <li data-view="2"> 七里香 </li>
    <li data-view="7">
    <a href="/2.mp3" singer="赵雷"> 理想 </a>
    </li>
    <li data-view="4" class="active">
    <a href="/3.mp3" singer="许巍"> 像风一样自由 </a>
    </li>
    <li data-view="6"><a href="/4.mp3" singer="安与骑兵"> 红山果 </a></li>
    <li data-view="5"><a href="/5.mp3" singer="薛之谦"> 意外 </a></li>
    <li data-view="5">
    <a href="/6.mp3" singer="马頔"> 但南山南 </a>
    </li>
    </ul>
    </div>'''
    results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)
    print(results)
    print(type(results))
    for result in results:
    print(result)
    print(result[0], result[1], result[2])

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    [('/2.mp3', '赵雷', ' 理想 '), ('/3.mp3', '许巍', ' 像风一样自由 '), ('/4.mp3', '安与骑兵', ' 红山果 '), ('/5.mp3', '薛之谦', ' 意外 '), ('/6.mp3', '马頔', ' 但南山南 ')]
    <class 'list'>
    ('/2.mp3', '赵雷', ' 理想 ')
    /2.mp3 赵雷 理想
    ('/3.mp3', '许巍', ' 像风一样自由 ')
    /3.mp3 许巍 像风一样自由
    ('/4.mp3', '安与骑兵', ' 红山果 ')
    /4.mp3 安与骑兵 红山果
    ('/5.mp3', '薛之谦', ' 意外 ')
    /5.mp3 薛之谦 意外
    ('/6.mp3', '马頔', ' 但南山南 ')
    /6.mp3 马頔 但南山南

    【6.5】re.sub 方法

    与字符串的 replace() 方法类似,sub() 方法可以对文本进行修改,sub() 方法第一个参数为匹配对象,第二个参数为替换成的字符串,如果要去掉匹配对象的话,可以赋值为空,第三个参数为原来的字符串

    1
    2
    3
    4
    5
    import re

    content = '87dsf4as2w4jh1k4kdl4'
    result = re.sub('\d+', '', content)
    print(result)

    输出结果:

    1
    dsfaswjhkkdl

    【6.5】re.compile() 方法

    compile() 方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import re

    content1 = '北京时间:2019-08-24 18:30'
    content2 = '伦敦时间:2019-08-24 11:30'
    content3 = '巴黎时间:2019-08-24 12:30'
    content4 = '外星时间:9019-99-66 50:30'
    pattern = re.compile('\d{2}:\d{2}')
    result1 = re.sub(pattern, '', content1)
    result2 = re.sub(pattern, '', content2)
    result3 = re.sub(pattern, '', content3)
    result4 = re.sub(pattern, '', content4)
    print(result1, result2, result3, result4)

    利用 compile() 方法将正则表达式编译成一个正则表达式对象,以便复用,然后用 sub() 方法去掉具体时间
    输出结果:

    1
    北京时间:2019-08-24  伦敦时间:2019-08-24  巴黎时间:2019-08-24  外星时间:9019-99-66

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - 正则表达式 - - - -
    - - - - - Python3 爬虫学习笔记 C05 - - /2019/08/23/A35-Python3-spider-C05/ - -
    Python3 爬虫学习笔记第五章 —— 【Selenium + 无界面浏览器】

    【5.1】关于无界面浏览器

    无界面(headless)浏览器,会把网站加载到内存并执行页面上的 JavaScript,因为不会展示图形界面,所以运行起来比完整的浏览器更高效。Selenium 搭配无界面浏览器使用,被称为爬虫利器,常用的无界面浏览器有:PhantomJS、Headless Chrome、Headless Firefox,其中,18年3月,PhantomJS 的作者在 GitHub 上宣布暂停开发 PhantomJS,现在使用 PhantomJS 会出现警告:UserWarning: Selenium support for PhantomJS has been deprecated, please use headless versions of Chrome or Firefox instead,所以推荐使用谷歌或者火狐的无界面浏览器

    【5.2】PhantomJS

    下载 PhantomJS:https://phantomjs.org/download.html
    path 为 PhantomJS 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数

    1
    2
    3
    4
    5
    6
    7
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\phantomjs-2.1.1\bin\phantomjs.exe'
    driver = webdriver.PhantomJS(executable_path=path)
    driver.get("https://www.itrhx.com")
    print(driver.page_source)
    driver.close()

    【5.3】Headless Chrome

    下载 Chromedriver:http://chromedriver.storage.googleapis.com/index.html
    需要本地有 Chrome 浏览器,path 为 Headless Chrome 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options

    chrome_options = Options()
    chrome_options.add_argument('--headless')
    chrome_options.add_argument('--disable-gpu')
    path = 'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    driver = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    driver.get("https://www.itrhx.com")
    print(driver.page_source)
    driver.close()

    【5.4】Headless Firefox

    下载 geckodriver:https://github.com/mozilla/geckodriver/releases/
    需要本地有 Firefox 浏览器,path 为 Headless Firefox 路径,如果系统配置了环境变量,就不用手动指定 executable_path 参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium.webdriver import Firefox
    from selenium.webdriver.firefox.options import Options

    options = Options()
    options.add_argument('-headless')
    path = 'F:\PycharmProjects\Python3爬虫\geckodriver.exe'
    driver = Firefox(executable_path=path, firefox_options=options)
    driver.get("https://www.itrhx.com")
    print(driver.page_source)
    driver.close()

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - Selenium - - 无界面浏览器 - - - -
    - - - - - 常见 User-Agent 大全 - - /2019/08/23/A34-UserAgent/ - -

    User Agent 中文名为用户代理,简称 UA,是一个特殊字符串头,使得服务器能够识别客户使用的操作系统及版本、CPU 类型、浏览器及版本、浏览器渲染引擎、浏览器语言、浏览器插件等。Python 爬虫通过伪装 UA 可以绕过某些检测。

    以下为搜集的常见的各浏览器的 User-Agent,其中:

    • 安卓操作系统:Android 7.1.1;OPPO R9sk Build/NMF26F
    • PC操作系统:Windows 10 64位 10.0.18362.10000
    • 其他操作系统:iOS、Backerry、WebOS、Symbian、Windows Phone

    相关链接:

    • 手机User-Agent大全:http://www.fynas.com/ua
    • User-Agent在线检测:http://www.user-agent.cn/
    • 常用User-Agent大全:http://www.jsons.cn/useragent/

      Windows10

    • Windows10 / Chrome 75.0.3770.142
      Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36

    • Windows10 / Firefox 69.0b15
      Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0

    • Windows10 / Opera 63.0.3368.43
      Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36 OPR/63.0.3368.43

    • Windows10 / Edge 44.18362.1.0
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.18362

    • Windows10 / IE 11.10000.18362.0
      User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; LCTE; rv:11.0) like Gecko

    • Windows10 x64 / Safari 5.1.4(7534.54.16)
      Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/534.54.16 (KHTML, like Gecko) Version/5.1.4 Safari/534.54.16

    • Windows10 / QQ浏览器 10.5(3739)
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3722.400 QQBrowser/10.5.3739.400

    • Windows10 / 360安全浏览器 10.0.1977.0
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36 QIHU 360SE

    • Windows10 / 360极速浏览器 11.0.2179.0
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36 QIHU 360EE

    • Windows10 / UC浏览器 6.2.3964.2
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 UBrowser/6.2.3964.2 Safari/537.36

    • Windows10 / 搜狗浏览器 8.5.10.31270
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0

    • Windows10 / 猎豹浏览器 6.5.115.19331.8001
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36 LBBROWSER

    • Windows10 / 傲游浏览器 5.2.7.5000
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36

    • Windows10 / 2345加速浏览器 10.1.0.19399
      Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3947.100 Safari/537.36

    Android

    • Android / Chrome 76.0.3809.111
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.111 Mobile Safari/537.36

    • Android / Firefox 68.0.2
      Mozilla/5.0 (Android 7.1.1; Mobile; rv:68.0) Gecko/68.0 Firefox/68.0

    • Android / Opera 53.0.2569.141117
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.157 Mobile Safari/537.36 OPR/53.0.2569.141117

    • Android / Edge 42.0.2.3819
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.2.3819

    • Android / QQ浏览器 9.6.1.5190
      Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/66.0.3359.126 MQQBrowser/9.6 Mobile Safari/537.36

    • Android / OPPO浏览器 10.5.1.2_2c91537
      Mozilla/5.0 (Linux; U; Android 7.1.1; zh-cn; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 OppoBrowser/10.5.1.2

    • Android / 360浏览器 8.2.0.162
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/62.0.3202.97 Mobile Safari/537.36

    • Android / 360极速浏览器 1.0.100.1078
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 360 Alitephone Browser (1.5.0.90/1.0.100.1078) mso_sdk(1.0.0)

    • Android / UC浏览器 12.6.0.1040
      Mozilla/5.0 (Linux; U; Android 7.1.1; zh-CN; OPPO R9sk Build/NMF26F) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/57.0.2987.108 UCBrowser/12.6.0.1040 Mobile Safari/537.36

    • Android / 猎豹浏览器 5.12.3
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 LieBaoFast/5.12.3

    • Android / 百度浏览器 7.19
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/48.0.2564.116 Mobile Safari/537.36 T7/9.1 baidubrowser/7.19.13.0 (Baidu; P1 7.1.1)

    • Android / 搜狗浏览器 5.22.8.71677
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/68.0.3440.106 Mobile Safari/537.36 AWP/2.0 SogouMSE,SogouMobileBrowser/5.22.8

    • Android / 2345浏览器 11.0.1
      Mozilla/5.0 (Linux; Android 7.1.1; OPPO R9sk Build/NMF26F; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/70.0.3538.80 Mobile Safari/537.36 Mb2345Browser/11.0.1

    其他

    • iPhone3
      Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/1A542a Safari/419.3

    • iPhone4
      Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7

    • iPhone6s
      Mozilla/5.0 (iPhone 6s; CPU iPhone OS 11_4_1 like Mac OS X) AppleWebKit/604.3.5 (KHTML, like Gecko) Version/11.0 MQQBrowser/8.3.0 Mobile/15B87 Safari/604.1 MttCustomUA/2 QBWebViewType/1 WKType/1

    • iPad
      Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B334b Safari/531.21.10

    • iPod
      Mozilla/5.0 (iPod; U; CPU iPhone OS 4_3_3 like Mac OS X; en-us) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5

    • BlackBerry
      Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, like Gecko) Version/6.0.0.337 Mobile Safari/534.1+

    • WebOS HP Touchpad
      Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0

    • Nokia N97
      Mozilla/5.0 (SymbianOS/9.4; Series60/5.0 NokiaN97-1/20.0.019; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) BrowserNG/7.1.18124

    • Windows Phone Mango
      Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; HTC; Titan)

    ]]>
    - - - - - Python3 学习笔记 - - 学习经验 - - - - - - - 爬虫 - - User-Agent - - - -
    - - - - - Selenium 显式等待条件及其含义 - - /2019/08/23/A33-selenium/ - -
    等待条件含义
    title_is标题是某内容
    title_contains标题包含某内容
    presence_of_element_located节点加载出,传入定位元组,如 (By.ID, ‘p’)
    visibility_of_element_located节点可见,传入定位元组
    visibility_of可见,传入节点对象
    presence_of_all_elements_located所有节点加载出
    text_to_be_present_in_element某个节点文本包含某文字
    text_to_be_present_in_element_value某个节点值包含某文字
    frame_to_be_available_and_switch_to_it frame加载并切换
    invisibility_of_element_located节点不可见
    element_to_be_clickable节点可点击
    staleness_of判断一个节点是否仍在 DOM,可判断页面是否已经刷新
    element_to_be_selected节点可选择,传节点对象
    element_located_to_be_selected节点可选择,传入定位元组
    element_selection_state_to_be传入节点对象以及状态,相等返回 True,否则返回 False
    element_located_selection_state_to_be传入定位元组以及状态,相等返回 True,否则返回 False
    alert_is_present是否出现 Alert

    更多等待条件极其用法介绍:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions

    Selenium 的使用:https://www.itrhx.com/2019/08/22/A32-Python3-spider-C04/

    ]]>
    - - - - - Python3 学习笔记 - - 学习经验 - - - - - - - 爬虫 - - Selenium - - - -
    - - - - - Python3 爬虫学习笔记 C04 - - /2019/08/23/A32-Python3-spider-C04/ - -
    Python3 爬虫学习笔记第四章 —— 【自动化测试工具 Selenium】

    Selenium 是一个用于 Web 应用程序测试的工具。Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。支持的浏览器包括IE(7, 8, 9, 10, 11),Mozilla Firefox,Safari,Google Chrome,Opera等。利用它可以驱动浏览器执行特定的动作,如点击、下拉等操作,同时还可以获取浏览器当前呈现的页面的源代码,做到可见即可爬。对于一些 JavaScript 动态渲染的页面来说,此种抓取方式非常有效。本文重点以 Selenium 使用谷歌浏览器的 Webdriver 为例。

    【4.1】下载驱动

    使用 Selenium 操作不同浏览器,需要不同浏览器相应的驱动支持:

    浏览器驱动名称下载地址备注
    谷歌浏览器chromedriver点击进入下载页面需要根据自己浏览器的版本下载不同版本的驱动
    火狐浏览器geckodriver点击进入下载页面需要根据自己的操作系统下载对应的驱动
    IEIEDriverServer点击进入下载页面根据自己 selenium 版本和系统版本下载对应版本的驱动, selenium 版本可以在cmd中输入pip show selenium查看

    【4.2】声明浏览器对象

    不同浏览器的对象声明方法:

    1
    2
    3
    4
    5
    6
    7
    from selenium import webdriver

    browser = webdriver.Chrome() # 谷歌浏览器
    browser = webdriver.Firefox() # 火狐浏览器
    browser = webdriver.Edge() # Edge
    browser = webdriver.PhantomJS() # PhantomJS无界面浏览器
    browser = webdriver.Safari() # Safari浏览器

    【4.3】访问页面

    1
    2
    3
    4
    5
    6
    7
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    print(browser.page_source)
    browser.close()

    运行代码就会自动打开谷歌浏览器,实现了用 get() 方法访问 www.itrhx.com ,path 里面的内容是谷歌浏览器驱动的目录, r 表示不转义,使用真实字符。print(browser.page_source) 表示打印页面源代码

    【4.4】启动参数

    Chrome Options 是一个 Chrome 的参数对象,在此对象中使用 add_argument() 方法可以添加启动参数,添加完毕后可以在初始化 Webdriver 对象时将此 Options 对象传入,则可以实现以特定参数启动Chrome。
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'

    # 实例化一个启动参数对象
    chrome_options = Options()
    # 添加启动参数
    chrome_options.add_argument('--window-size=1366,768')
    # 将参数对象传入Chrome,则启动了一个设置了窗口大小的Chrome
    browser = webdriver.Chrome(executable_path=path, chrome_options=chrome_options)
    browser.get('http://www.itrhx.com')

    这样就启动了一个1366x768分辨率的浏览器
    常见的启动参数:

    启动参数作用
    –user-agent=””设置请求头的 User-Agent
    –window-size=xxx, xxx设置浏览器分辨率
    –headless无界面运行
    –start-maximized最大化运行
    –incognito隐身模式
    –disable-javascript禁用javascript
    –disable-infobars禁用“浏览器正在被自动化程序控制”的提示

    所有的启动参数:https://peter.sh/experiments/chromium-command-line-switches/

    【4.5】查找节点

    Selenium 可以驱动浏览器完成各种操作,比如填充表单、模拟点击等。要完成这些操作,实现要知道在哪里点击,哪里填充,这就是 Selenium 节点查找

    【4.5.1】查找单个节点

    所有获取单个节点的方法:

    • find_element_by_id 【通过元素的 id 来选择】
      例:<div id='bdy-inner'>test</div>,查找:driver.find_element_by_id('bdy-inner')

    • find_element_by_name 【通过元素的 name 来选择】
      例:<input name="username" type="text" />,查找:driver.find_element_by_name('password')

    • find_element_by_xpath 【通过 xpath 选择】
      例:<form id="loginForm">,查找:driver.find_element_by_xpath("//form[@id='loginForm']")

    • find_element_by_link_text 【通过链接地址选择】
      例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('continue')

    • find_element_by_partial_link_text 【通过链接的部分地址选择】
      例:<a href="continue.html">continue</a>,查询:driver.find_element_by_link_text('cont')

    • find_element_by_tag_name 【通过元素的名称选择】
      例:<h1>welcome<h1>,查询:driver.find_element_by_tag_name('h1')

    • find_element_by_class_name 【通过元素的 class 选择】
      例:<p class="content">welcome to TRHX'S BLOG!</p>,查询:driver.find_element_by_class_name('content')

    • find_element_by_css_selector 【通过元素的 class 选择】
      例:<div class='bdy-inner'>test</div>,查询:driver.find_element_by_css_selector('div.bdy-inner')

    • find_element() 【通用方法,需要传递两个参数:查找方式 By 和值】
      例:driver.find_element_by_id('inner') 等价于 find_element(By.ID, inner),使用时需要from selenium.webdriver.common.by import By

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    blog_title = browser.find_elements_by_class_name(('title'))
    print(blog_title[0].text)
    browser.close()

    输出结果:

    1
    TRHX'S BLOG

    【4.5.2】查找多个节点

    所有获取多个节点的方法:(与查找单个节点的区别是 element 多加了个 s)

    • find_elements_by_id
    • find_elements_by_name
    • find_elements_by_xpath
    • find_elements_by_link_text
    • find_elements_by_partial_link_text
    • find_elements_by_tag_name
    • find_elements_by_class_name
    • find_elements_by_css_selector
    • find_elements()

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from selenium import webdriver
    from selenium.webdriver.common.by import By

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    article_title = browser.find_elements(By.XPATH, "//h2[@class='title']")
    print(article_title)
    browser.close()

    【4.6】节点交互

    Selenium 可以驱动浏览器来执行一些操作,也就是说可以让浏览器模拟执行一些动作。称为节点交互,比较常见的用法有:

    • send_keys:模拟按键输入
    • clear:清除元素的内容
    • click:单击元素
    • submit:提交表单

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    search = browser.find_element_by_xpath('//div[@class="cover-wrapper"]/cover/div/form/input')
    search.send_keys("Python")
    search.send_keys(Keys.ENTER)

    此处模拟了键盘,需要导入键盘类 Keys(),send_keys(Keys.ENTER)表示模拟回车键,程序首先打开 www.itrhx.com ,也就是我的博客,然后通过 xpath 找到搜索框,输入 Python 并回车,等待结果显示出来
    更多节点交互动作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement

    【4.7】动作链

    Selenium 还有另外一些操作,它们没有特定的执行对象,比如鼠标拖曳、键盘按键等,这些动作用另一种方式来执行,那就是动作链。以一个拖曳实例为例:http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from selenium import webdriver
    from selenium.webdriver import ActionChains

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
    browser.get(url)
    browser.switch_to.frame('iframeResult')
    source = browser.find_element_by_css_selector('#draggable')
    target = browser.find_element_by_css_selector('#droppable')
    actions = ActionChains(browser)
    actions.drag_and_drop(source, target)
    actions.perform()

    依次选中要拖曳的节点和拖曳到的目标节点,接着声明 ActionChains 对象并将其赋值为 actions 变量,然后通过调用 actions 变量的 drag_and_drop() 方法,再调用 perform() 方法执行动作,此时就完成了拖曳操作,更多动作链操作:https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains

    【4.8】执行 JavaScript

    Selenium API 并没有提供执行 JavaScript 的方法,但是实际上是可以实现的。比如,下拉进度条,它可以直接模拟运行 JavaScript,此时使用 execute_script() 方法即可实现
    示例:

    1
    2
    3
    4
    5
    6
    7
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    browser.execute_script('alert("已到达最底端!")')

    以上代码实现了利用 execute_script() 方法将进度条下拉到最底部,然后弹出 alert 提示框。

    【4.9】禁用加载

    使用Selenium 时,限制图片和 Javascript 执行,从而提高网页加载速度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'

    options = webdriver.ChromeOptions()
    prefs = {
    'profile.default_content_setting_values': {
    'images': 2,
    'notifications' : 2, # 禁用弹窗
    'javascript': 2 # 2即为禁用的意思
    }
    }
    options.add_experimental_option('prefs', prefs)
    browser = webdriver.Chrome(executable_path=path, chrome_options=options)
    browser.get('http://www.itrhx.com')

    【4.10】获取节点信息

    通过 page_source 属性可以获取网页的源代码,然后可以使用解析库(如正则表达式、Beautiful Soup等)来提取相关信息,Selenium 已经提供了选择节点的方法,返回的是 WebElement 类型,它也有相关的方法和属性来直接提取节点信息,如属性、文本等。就不需要再次使用解析库来提取信息了

    【4.10.1】获取属性

    使用 get_attribute() 方法来获取节点的属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    url = 'http://www.itrhx.com'
    browser.get(url)
    meta = browser.find_element_by_id('header-meta')
    print(meta)
    print(meta.get_attribute('class'))

    输出结果:

    1
    2
    <selenium.webdriver.remote.webelement.WebElement (session="d03cdaa497441d2e2a5161139b4a7ea5", element="83f8fff9-60d7-4e9a-ade3-a8e97c9f0844")>
    meta

    【4.10.2】获取文本值

    每个 WebElement 节点都有 text 属性,直接调用这个属性就可以得到节点内部的文本信息,相当于 Beautiful Soup 的 get_text() 方法、pyquery 的 text() 方法
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    url = 'http://www.itrhx.com'
    browser.get(url)
    footer_info = browser.find_element_by_id('footer')
    print(footer_info.text)

    输出结果:

    1
    2
    3
    Copyright 2018-2019 TRHX'BLOG   |   鄂ICP备19003281号-4  |   本站已勉强存活了 376 天 20 小时 57 分 52 秒   |   站点地图  |   站长统计

    PoweredHexo HostedGitHub DNRAliyun CDNjsDelivr ThemeMaterial X BY-NC-SA 4.0 Link996.ICU UV4898 PV22066 WordCount54.9k

    【4.10.3】获取 ID、位置、标签名、大小

    其他属性,比如 id 属性可以获取节点 id,location 属性可以获取该节点在页面中的相对位置,tag_name 属性可以获取标签名称,size 属性可以获取节点的大小等
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    url = 'http://www.itrhx.com'
    browser.get(url)
    readmore = browser.find_element_by_class_name('readmore')
    print(readmore.id)
    print(readmore.location)
    print(readmore.tag_name)
    print(readmore.size)

    输出结果:

    1
    2
    3
    4
    7df561d3-7ea4-4b90-96aa-64044060bb47
    {'x': 50, 'y': 1063}
    div
    {'height': 39, 'width': 465}

    【4.11】延时等待

    在 Selenium 中,get() 方法会在网页框架加载结束后结束执行,某些页面有额外的 Ajax 请求,若此时立即获取 page_source,可能并不是浏览器完全加载完成的页面,这里需要延时等待一定时间,确保节点已经加载出来

    【4.11.1】隐式等待

    当查找节点的时候,节点并没有立即出现,隐式等待将等待一段时间再查找该节点,使用 implicitly_wait() 方法可以实现隐式等待

    1
    2
    3
    4
    5
    6
    7
    8
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.implicitly_wait(10)
    browser.get('https://www.itrhx.com')
    readmore = browser.find_element_by_class_name('readmore')
    print(readmore)

    【4.11.2】显式等待

    指定要查找的节点,然后指定一个最长等待时间。如果在规定时间内加载出来了这个节点,就立即返回查找的节点,果到了规定时间依然没有加载出该节点,则抛出超时异常

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.implicitly_wait(10)
    browser.get('https://www.itrhx.com')
    wait = WebDriverWait(browser, 10)
    footer_info = wait.until(EC.presence_of_element_located((By.ID, 'footer')))
    print(footer_info)

    引入 WebDriverWait 对象,指定最长等待时间,调用它的 until() 方法,传入要等待条件 expected_conditions。比如,这里传入了 presence_of_element_located 这个条件,代表节点出现的意思,其参数是节点的定位元组,也就是 ID 为 footer 的节点。

    这样可以做到的效果就是,在 10 秒内如果 ID 为 footer 的节点成功加载出来,就返回该节点;如果超过 10 秒还没有加载出来,就抛出异常。

    加载成功时输出结果:

    1
    <selenium.webdriver.remote.webelement.WebElement (session="4ca7015891fded627ab680d9462e9361", element="3a80235c-9824-420b-b827-662638422765")>

    加载失败时输出结果:

    1
    2
    3
    4
    5
    TimeoutException Traceback (most recent call last)
    <ipython-input-4-f3d73973b223> in <module>()
    7 browser.get('https://www.itrhx.com')
    8 wait = WebDriverWait(browser, 10)
    ----> 9 input = wait.until(EC.presence_of_element_located((By.ID, 'footer')))

    【4.12】Cookies

    使用 Selenium,可以方便地对 Cookies 进行获取、添加、删除等操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from selenium import webdriver

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.zhihu.com/explore')
    print(browser.get_cookies())
    browser.add_cookie({'name': 'TRHX', 'domain': 'www.zhihu.com', 'value': 'germey'})
    print(browser.get_cookies())
    browser.delete_all_cookies()
    print(browser.get_cookies())

    访问知乎,加载完成后,浏览器已经生成了 Cookies。调用 get_cookies() 方法获取所有的 Cookies。然后再添加一个 Cookie,传入一个字典,有 name、domain 和 value 等内容。接下来,再次获取所有的 Cookies。可以发现,结果就多了这一项新加的 Cookie。最后,调用 delete_all_cookies() 方法删除所有的 Cookies。再重新获取,发现结果就为空了
    输出结果:

    1
    2
    3
    [{'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}]
    [{'domain': 'www.zhihu.com', 'httpOnly': False, 'name': 'TRHX', 'path': '/', 'secure': True, 'value': 'germey'}, {'domain': 'zhihu.com', 'expiry': 1661065738.754333, 'httpOnly': False, 'name': 'd_c0', 'path': '/', 'secure': False, 'value': '"AODi_Lod7g-PTrrXUgXb1N4MkbStCrbNlD4=|1566457741"'}, {'domain': 'zhihu.com', 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'aba68431-9daf-4b62-a67a-023c1a24f0e8'}, {'domain': 'zhihu.com', 'expiry': 1629529738.75427, 'httpOnly': False, 'name': '_zap', 'path': '/', 'secure': False, 'value': 'b6f63cfc-a525-4ae6-a7bf-6384bd1e0548'}, {'domain': 'www.zhihu.com', 'expiry': 1566458637.754178, 'httpOnly': False, 'name': 'tgw_l7_route', 'path': '/', 'secure': False, 'value': '116a747939468d99065d12a386ab1c5f'}]
    [{'domain': 'zhihu.com', 'expiry': 1644217741.489889, 'httpOnly': False, 'name': '_xsrf', 'path': '/', 'secure': False, 'value': 'WNOjpDbNmz36B4nG1lzSAuPdTyORMX6J'}]

    【4.13】前进与后退

    使用 back() 方法后退,使用 forward() 方法前进,与浏览器的前进后退一样
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    from selenium import webdriver
    import time

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com/')
    browser.get('https://www.baidu.com/')
    browser.get('https://www.zhihu.com/')
    browser.back()
    time.sleep(1)
    browser.forward()
    browser.close()

    【4.14】选项卡

    和浏览器一样,在 Selenium 中也可以新建一个选项卡

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from selenium import webdriver
    import time

    path = r'F:\PycharmProjects\Python3爬虫\chromedriver.exe'
    browser = webdriver.Chrome(executable_path=path)
    browser.get('https://www.itrhx.com')
    browser.execute_script('window.open()')
    print(browser.window_handles)
    browser.switch_to.window(browser.window_handles[1])
    browser.get('https://www.baidu.com')
    time.sleep(1)
    browser.switch_to.window(browser.window_handles[0])
    browser.get('https://www.zhihu.com')

    首先访问我的博客,然后调用了 execute_script() 方法,传入 window.open() 这个 JavaScript 语句开启一个新的选项卡。再调用 window_handles 属性获取当前开启的所有选项卡,返回的是选项卡的代号列表。调用 switch_to_window() 方法来切换选项卡,其中参数是选项卡的代号。
    输出的选项卡代号列表:

    1
    ['CDwindow-C9CADF1ED28CE44970655238552A8DCF', 'CDwindow-538D7F81E467746B7BB2D9D82E2D036E']

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - Selenium - - - -
    - - - - - Python3 爬虫学习笔记 C03 - - /2019/08/23/A31-Python3-spider-C03/ - -
    Python3 爬虫学习笔记第三章 ——【Ajax 数据爬取】

    【3.1】Ajax 简介

    Ajax — Asynchronous Javascript And XML(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

    【3.2】解析真实地址提取

    以豆瓣电影动作片排行榜为例,地址为:https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action= ,首先使用常用方法来爬取电影信息:

    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    url = 'https://movie.douban.com/typerank?type_name=%E5%8A%A8%E4%BD%9C&type=5&interval_id=100:90&action='

    headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"}
    response = requests.get(url, headers=headers)

    print(response.text)

    得到的数据里面我们并没有找到电影相关信息:
    1
    再次分析页面,发现鼠标下滑的时候,页面不刷新,URL 也不变,但是会加载新数据,那么此处就运用了 Ajax,可以使用抓包工具或者浏览器控制台来捕获 Ajax 接口,获取其真实地址,XHR 是 Ajax 特殊的请求类型,返回的是 json 数据,利用浏览器控制台过滤 XHR,随便点击一条请求,可以看到其 Request URL,也就是真实地址,点击 Preview 就可以看到返回的 json 数据。
    2
    3
    同样,我们可以使用 Fiddler 抓包软件抓取 Ajax 接口:
    4
    分析其真实地址为:https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&start=20&limit=20 ,多下滑几次,只有 start 参数发生了改变,观察变化可知:每一次页面将多出20个电影信息,start 为从第几个电影开始,由此就不难进行数据抓取了

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import requests

    url = 'https://movie.douban.com/j/chart/top_list?type=5&interval_id=100%3A90&action=&'

    page = int(input('请输入想要第几页的数据:'))
    data = {
    'start': (page - 1)*20,
    'limit': '20',
    }
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36',
    }
    response = requests.get(url, params=data, headers=headers)
    print(response.text)

    运行代码即可得到电影排行信息:
    5

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - Ajax - - - -
    - - - - - Python3 爬虫学习笔记 C02 - - /2019/08/23/A30-Python3-spider-C02/ - -
    Python3 爬虫学习笔记第二章 ——【基本库 requests 的使用】

    【2.1】 requests 简介

    在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库

    requests 模块需要使用 pip install 命令安装安装,相比 urllib,它的 API 更加人性化,使用 requests 可以让 Cookies、登录验证、代理设置等操作更加简便,官网介绍:http://cn.python-requests.org

    【2.2】 requests 基本用法

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests
    r = requests.get('https://www.itrhx.com/')
    print(type(r))
    print(r.encoding)
    print(r.status_code)
    print(r.cookies)
    print(r.json)
    print(r.text)
    print(r.content)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <class 'requests.models.Response'>
    utf-8
    200
    <RequestsCookieJar[]>
    <bound method Response.json of <Response [200]>>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    ......

    • r.encoding:服务器内容使用的文本编码;
    • r.status_code:响应状态码,200 代表成功,4xx 代表客户端错误,5xx 服务器响应错误;
    • r.cookies:返回 Cookies;
    • r.json:Requests 内置 JSON 解码器;
    • r.text:服务器响应内容,根据响应头部的字符编码自动解码;
    • r.content:字节方式的响应体,自动解码 gzip 和 deflate 编码的响应。

    【2.3】 requests 构建 GET 请求

    【2.3.1】 基本用法

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests

    data = {
    'name': 'TRHX',
    'age': '20'
    }
    r = requests.get("http://httpbin.org/get", params=data)
    print('编码后的URL:', r.url)
    print('字符串方式的响应体:', r.text)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    编码后的URL: http://httpbin.org/get?name=TRHX&age=20
    字符串方式的响应体: {
    "args": {
    "age": "20",
    "name": "TRHX"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/get?name=TRHX&age=20"
    }

    【2.3.2】 二进制数据抓取

    以抓取 GitHub 站点图标为例:

    1
    2
    3
    4
    5
    import requests

    r = requests.get("https://github.com/favicon.ico")
    with open('favicon.ico', 'wb') as f:
    f.write(r.content)

    该代码将会保存站点图标到本地,其他的,比如音频,视频文件都是由二进制码组成的,皆可使用该方法

    【2.3.3】 添加 headers

    headers 的作用:部分页面禁止 Python 爬虫对其进行爬取,而添加 headers 就可以模拟成浏览器取访问网站,实现数据的爬取,headers 可以在任意网页 F12 检查控制台里面找到,headers 最重要的是 “User-Agent” 字段


    01

    以为例知乎,只有加了 headers 才能正常爬取,否则会返回 400 Bad Request 没有任何数据

    1
    2
    3
    4
    5
    6
    import requests

    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'
    }
    r = requests.get("https://www.zhihu.com/explore", headers=headers)
    print(r.text)

    【2.4】 requests 构建 POST 请求

    示例:

    1
    2
    3
    4
    5
    import requests

    data = {'name': 'TRHX', 'age': '20'}
    r = requests.post("http://httpbin.org/post", data=data)
    print(r.text)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "age": "22",
    "name": "germey"
    },
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "18",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }

    有关 POST 和 GET 两种请求的一些区别:

    • POST 更加安全,不会作为 URL 的一部分,不会被缓存,保存在服务器日志、以及浏览器浏览记录中;
    • POST 发送的数据更大,GET 有 URL 长度限制;
    • POST 可以发送更多的数据类型,GET 只能发送 ASCII 字符;
    • POST 比 GET 慢;
    • POST 查询参数在 WebForms 保存,GET 查询参数在 QueryString 保存;
    • POST 用数据的修改和写入,GET 一般用于搜索排序和筛选之类的操作。

    【2.5】 requests 高级用法

    【2.5.1】 上传文件

    示例:

    1
    2
    3
    4
    5
    import requests

    files = {'file': open('test.png', 'rb')}
    r = requests.post('http://httpbin.org/post', files=files)
    print(r.text)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    {
    "args": {},
    "data": "",
    "files": {
    "file": "data:application/octet-stream;base64,iVBOR......"
    },
    "form": {},
    "headers": {
    "Accept": "*/*",
    "Accept-Encoding": "gzip, deflate",
    "Content-Length": "81383",
    "Content-Type": "multipart/form-data; boundary=e36a8686cd77c79dc02bfe9d1b010f08",
    "Host": "httpbin.org",
    "User-Agent": "python-requests/2.22.0"
    },
    "json": null,
    "origin": "171.115.102.230, 171.115.102.230",
    "url": "https://httpbin.org/post"
    }

    【2.5.2】 使用 Cookies

    对于需要登录后才能获取数据的网页,可以将账号登录的 Cookies 添加到 headers 来实现网页登录爬取,Cookies 可以抓包获取,代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import requests

    headers = {
    'Cookie': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    'Host': 'www.zhihu.com',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36',
    }
    r = requests.get('https://www.zhihu.com', headers=headers)
    print(r.text)

    【2.5.3】 会话维持

    • 背景介绍:利用 get() 或者 post() 方法来模拟网页请求,相当于是不同的会话,可以理解为用两个浏览器打开了不同的网页;

    • 运用场景:首先使用 post() 方法登录网页,然后再使用 get() 方法请求某个页面信息,如果不利用会话维持,将无法获取页面数据

    • 维持方法:①两次请求设置一样的 cookies,缺点:繁琐;②使用 Session 对象。

      Session 对象使用示例:

      1
      2
      3
      4
      5
      6
       import requests

      s = requests.Session()
      s.get('http://httpbin.org/cookies/set/number/123456789')
      r = s.get('http://httpbin.org/cookies')
      print(r.text)

    输出结果成功获取到设置的 cookies:

    1
    2
    3
    4
    5
    {
    "cookies": {
    "number": "123456789"
    }
    }

    【2.5.4】 SSL 证书验证

    SSL 证书是数字证书的一种,由受信任的数字证书颁发机构 CA 在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能,网站带有 HTTPS 就表明有 SSL 证书

    requests 提供了证书验证的功能。当发送 HTTP 请求的时候,它会检查 SSL 证书,verify 参数可以控制是否检查此证书。如果不加 verify 参数,默认为 True,会自动验证。当一个页面的 SSL 证书没有被官方机构认证时,打开页面就会提示“您的连接不是私密连接”,如果没有设置 verify 参数,将会报以下错误:

    1
    requests.exceptions.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)

    设置 verify 参数代码示例:

    1
    2
    3
    4
    import requests

    response = requests.get('https://www.itrhx.com', verify=False)
    print(response.text)

    【2.5.5】 设置代理

    为什么要设置代理:某些网页有反爬虫机制,频繁请求网页就会出现验证码等,还有可能直接封掉 IP,导致爬取失败;这种情况下就可以设置 proxies 参数。
    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    import requests

    proxies = {
    'http': 'http://10.10.1.10:1010',
    'https': 'http://10.10.1.10:1020',
    }

    requests.get('https://www.itrhx.com', proxies=proxies)

    免费代理可在西刺代理找到

    【2.5.6】 超时设置

    与 urllib.request.urlopen() 类似,requests 也可以设置 timeout 参数,请求分为两个阶段:连接和读取

    设置连接和读取时间总和:

    1
    2
    3
    4
    import requests

    r = requests.get('https://www.itrhx.com', timeout=1)
    print(r.status_code)

    分别设置连接和读取时间:

    1
    2
    3
    4
    import requests

    r = requests.get('https://www.itrhx.com', timeout=(5, 10))
    print(r.status_code)

    永久等待:

    1
    2
    3
    4
    5
    6
    import requests

    # 两种方法实现
    # r = requests.get('https://www.itrhx.com')
    r = requests.get('https://www.itrhx.com', timeout=None)
    print(r.status_code)

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - requests - - - -
    - - - - - Python3 爬虫学习笔记 C01 - - /2019/08/23/A29-Python3-spider-C01/ - -
    Python3 爬虫学习笔记第一章 ——【基本库 urllib 的使用】

    【1.1】 urllib 简介

    在 Python 中有两种方式可以发送 HTTP 请求,分别是自带的 urllib 库和第三方的 requests 库

    urllib 库:Python 内置的 HTTP 请求库,无需额外安装即可使用;Python 2 中有 urllib 和 urllib2 两个库来实现请求的发送,Python 3 中统一为 urllib。官方文档:https://docs.python.org/3/library/urllib.html

    urllib 所包含的常用模块:
    • urllib.request:模拟发送请求;
    • urllib.error:异常处理模块,用于捕获异常;
    • urllib.parse:解析、拆分、合并URL;
    • urllib.robotparser:读取网站的 robots.txt 文件,判断哪些内容可以爬取。
    urllib.request 所包含的常用方法:
    • urllib.request.urlopen():打开网址URL,这可以是一个字符串或一个 Request对象;
    • urllib.request.Request():在请求的时候传入一些 headers 等信息;
    • urllib.request.urlretrieve():将获取的URL的内容写到文件目录中去。
    urllib.error 所包含的两个异常:
    • URLError:继承自 OSError 类,是 error 异常模块的基类,由 request 模块产生的异常都可以通过捕获这个类来处理。
    • HTTPError:是 URLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。
    urllib.parse 所包含的常用方法:
    • urllib.parse.urlencode():将字典参数序列化为 GET 请求参数;
    • urllib.parse.parse_qs():将 GET 请求参数反序列化转回字典;
    • urllib.parse.parse_qsl():将参数转化为元组组成的列表;
    • urllib.parse.urlparse():对 URL 进行分段(返回6个结果);
    • urllib.parse.urlunparse():对 URL 进行组合(长度必须为6);
    • urllib.parse.urlsplit():对 URL 进行分段(不单独解析params部分,返回5个结果);
    • urllib.parse.urlunsplit():对 URL 进行组合(长度必须为5);
    • urllib.parse.urljoin():对 URL 进行组合(没有长度限制,给定两个参数,自动分析 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果);
    • urllib.parse.quote():将内容转化为 URL 编码格式;
    • urllib.parse.unquote():对 URL 进行解码。
    urllib.robotparser 所包含的类:
    • RobotFileParser:根据网站的 robots.txt 文件来判断一个爬取爬虫是否有权限来爬取这个网页

    【1.2】 urllib.request 发送请求

    【1.2.1】 urllib.request.urlopen()

    【1.2.1.1】 基本使用方法

    urlopen() 函数的 API:

    1
    urllib.request.urlopen(url, data=None, [timeout,]*, cafile=None, capath=None, cadefault=False, context=None)

    基本使用:运行以下代码可得到 https://www.itrhx.com/ 的网页源代码:

    1
    2
    3
    4
    import urllib.request

    response = urllib.request.urlopen('https://www.itrhx.com/')
    print(response.read().decode('utf-8'))

    输出响应对象的类型和属性:

    1
    2
    3
    4
    5
    6
    7
    import urllib.request

    response = urllib.request.urlopen('https://www.itrhx.com/')
    print(type(response)) # 响应类型
    print(response.status) # 返回结果的状态码,200代表请求成功
    print(response.getheaders()) # 响应的头信息
    print(response.getheader('Server')) # 获取响应头的 server 值

    运行结果:

    1
    2
    3
    4
    <class 'http.client.HTTPResponse'>
    200
    [('Content-Type', 'text/html; charset=utf-8'), ('Server', 'GitHub.com'), ('Last-Modified', 'Sat, 17 Aug 2019 12:16:48 GMT'), ('ETag', '"5d57f030-10863"'), ('Access-Control-Allow-Origin', '*'), ('Expires', 'Sat, 17 Aug 2019 19:41:25 GMT'), ('Cache-Control', 'max-age=600'), ('X-Proxy-Cache', 'MISS'), ('X-GitHub-Request-Id', 'C748:735D:5B7461:619B95:5D58560B'), ('Content-Length', '67683'), ('Accept-Ranges', 'bytes'), ('Date', 'Sun, 18 Aug 2019 13:28:44 GMT'), ('Via', '1.1 varnish'), ('Age', '228'), ('Connection', 'close'), ('X-Served-By', 'cache-tyo19931-TYO'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '1'), ('X-Timer', 'S1566134924.190474,VS0,VE0'), ('Vary', 'Accept-Encoding'), ('X-Fastly-Request-ID', '25a69f8130fc9cae412d28990a724543d7d05e8b')]
    GitHub.com

    【1.2.1.2】 添加参数

    根据 urlopen() 函数的 API 可知,除了最基本的 URL 参数以外,我们还可以传递其他内容,比如 data(附加数据)、timeout(超时时间)等,以下用 data 和 timeout 参数举例说明。

    ● data 参数

    如果要添加 data 参数,需要使用 bytes 方法将参数转化为字节流编码格式的内容,即 bytes 类型。另外,如果传递了这个参数,则它的请求方式就不再是 GET 方式,而是 POST 方式。代码示例:

    1
    2
    3
    4
    5
    6
    import urllib.parse
    import urllib.request

    data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
    response = urllib.request.urlopen('http://httpbin.org/post', data=data)
    print(response.read())

    httpbin.org 站点提供 HTTP 请求测试,http://httpbin.org/post 用于测试 POST 请求,示例中传递一个值为 hello 的 word 参数。使用 bytes 方法,将其转码成 bytes(字节流)类型。该方法的第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode 方法来将参数字典转化为字符串;第二个参数指定编码格式为 utf8,运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    b'{
    "args": {},
    "data": "",
    "files": {},
    "form": {
    "word": "hello"
    },
    "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "10",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.6"
    },
    "json": null,
    "origin": "171.115.101.10, 171.115.101.10",
    "url": "https://httpbin.org/post"
    }'

    ● timeout 参数

    举例:

    1
    2
    3
    4
    import urllib.request

    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
    print(response.read())

    运行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    ...
    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "C:/Users/Lenovo/Desktop/1.py", line 2, in <module>
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
    ...
    urllib.error.URLError: <urlopen error timed out>

    timeout 设置为0.1,0.1秒过后服务器没有响应,便会抛出 URLError 异常
    进阶:使用 try except 语句抛出异常

    【1.2.2】 urllib.request.Request()

    Request() 方法可以在请求的时候传入一些 data、headers 等信息
    Request() 的构造方法:

    1
    class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

    构造方法各个参数的解释:

    • url:用于请求 URL,这是必传参数,其他都是可选参数。

    • data:如果要传,必须传 bytes(字节流)类型的。如果它是字典,可以先用 urllib.parse 模块里的 urlencode() 编码。

    • headers:是一个字典,它就是请求头,可以在构造请求时通过 headers 参数直接构造,也可以通过调用请求实例的 add_header() 方法添加。添加请求头最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。

    • origin_req_host:指的是请求方的 host 名称或者 IP 地址。

    • unverifiable:表示这个请求是否是无法验证的,默认是 False,意思就是说用户没有足够权限来选择接收这个请求的结果。例如,我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。

    • method:是一个字符串,用来指示请求使用的方法,比如 GET、POST 和 PUT 等。

    简单举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import urllib.request
    import urllib.parse

    url = 'http://www.baidu.com/'

    # 定制要伪装的头部
    headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36'
    }
    # 构建请求对象
    request = urllib.request.Request(url=url, headers=headers)
    # 发送请求
    response = urllib.request.urlopen(request)
    print(response.read().decode())

    【1.2.3】 urllib.request.urlretrieve()

    将获取到的 URL 内容保存到当前文件夹,简单举例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import urllib.request

    url = 'https://www.itrhx.com/images/trhx.png'

    # response = urllib.request.urlopen(image_url)
    # with open('trhx.png', 'wb') as fp:
    # fp.write(response.read())

    urllib.request.urlretrieve(url, 'trhx.png')

    【1.3】 urllib.error 异常处理

    【1.3.1】 URLError

    如果打开一个不存在的页面,就会出现 URLError 错误,该错误有一个 reason 属性,用于返回错误的原因。简单举例:

    1
    2
    3
    4
    5
    from urllib import request, error  
    try:
    response = request.urlopen('https://www.itrhx.com/index/')
    except error.URLError as e:
    print(e.reason)

    输出结果:

    1
    Not Found

    【1.3.2】 HTTPError

    URLError 的子类,专门用来处理 HTTP 请求错误,比如认证请求失败等。它有如下3个属性:

    • code:返回 HTTP 状态码,比如 404 表示网页不存在,500 表示服务器内部错误等。
    • reason:同父类一样,用于返回错误的原因。
    • headers:返回请求头。

    简单举例:

    1
    2
    3
    4
    5
    from urllib import request, error  
    try:
    response = request.urlopen('https://www.itrhx.com/index/')
    except error.HTTPError as e:
    print(e.code, e.reason, e.headers)

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    404 Not Found Content-Type: text/html; charset=utf-8
    Server: GitHub.com
    ETag: "5d57f030-7f2"
    Access-Control-Allow-Origin: *
    X-Proxy-Cache: MISS
    X-GitHub-Request-Id: 4B46:2F5D:6DE0F1:755BB2:5D5964C5
    Content-Length: 2034
    Accept-Ranges: bytes
    Date: Sun, 18 Aug 2019 14:50:41 GMT
    Via: 1.1 varnish
    Age: 252
    Connection: close
    X-Served-By: cache-tyo19951-TYO
    X-Cache: HIT
    X-Cache-Hits: 1
    X-Timer: S1566139842.563134,VS0,VE0
    Vary: Accept-Encoding
    X-Fastly-Request-ID: e9eb0a507be66a866bfaa7c5cc2e1c53b1f7ccab

    【1.3.3】 进阶用法

    因为 URLError 是 HTTPError 的父类,所以可以先选择捕获子类的错误,再去捕获父类的错误,前面的代码改进:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from urllib import request, error  

    try:
    response = request.urlopen('https://www.itrhx.com/index/')
    except error.HTTPError as e:
    print(e.reason, e.code, e.headers)
    except error.URLError as e:
    print(e.reason)
    else:
    print('Request Successfully')

    【1.4】 urllib.parse 解析 URL

    【1.4.1】 urllib.parse.urlencode()

    将字典参数序列化为 GET 请求参数,示例:

    1
    2
    3
    4
    5
    6
    7
    8
    from urllib.parse import urlencode
    data = {
    'ie': 'utf-8',
    'wd': 'TRHX',
    }
    base_url = 'http://www.baidu.com?'
    url = base_url + urlencode(data)
    print(url)

    输出结果:

    1
    http://www.baidu.com?ie=utf-8&wd=TRHX

    【1.4.2】 urllib.parse.parse_qs()

    与 urlencode() 相反,将 GET 请求参数反序列化转回字典,示例:

    1
    2
    3
    from urllib.parse import parse_qs
    query = 'name=TRHX&age=20'
    print(parse_qs(query))

    输出结果:

    1
    {'name': ['TRHX'], 'age': ['20']}

    【1.4.3】 urllib.parse.parse_qsl()

    将参数转化为元组组成的列表,示例:

    1
    2
    3
    from urllib.parse import parse_qsl
    query = 'name=TRHX&age=20'
    print(parse_qsl(query))

    输出 结果:

    1
    [('name', 'TRHX'), ('age', '20')]

    【1.4.4】 urllib.parse.urlparse()

    对 URL 进行分段,返回 6 个结果,示例:

    1
    2
    3
    from urllib.parse import urlparse
    result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
    print(type(result), result)

    输出结果:

    1
    <class 'urllib.parse.ParseResult'> ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

    返回结果为 ParseResult 类型的对象,含 scheme、netloc、path、params、query 和 fragment 6 个部分,依次代表协议、域名、路径、参数、查询条件、锚点

    【1.4.5】 urllib.parse.urlunparse()

    与 urlparse() 相反,对 URL 进行组合,传入的参数是一个可迭代对象,长度必须是 6,否则会抛出参数数量不足或者过多的问题,示例:

    1
    2
    3
    from urllib.parse import urlunparse  
    data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
    print(urlunparse(data))

    输出结果:

    1
    http://www.baidu.com/index.html;user?a=6#comment

    【1.4.6】 urllib.parse.urlsplit()

    与 urlparse() 方法相似,但是它不再单独解析 params 部分,只返回 5 个结果。params 会合并到 path 中,示例:

    1
    2
    3
    from urllib.parse import urlsplit  
    result = urlsplit('http://www.baidu.com/index.html;user?id=5#comment')
    print(result)

    输出结果:

    1
    SplitResult(scheme='http', netloc='www.baidu.com', path='/index.html;user', query='id=5', fragment='comment')

    【1.4.7】 urllib.parse.urlunsplit()

    与 urlunparse() 方法类似,对 URL 进行组合,传入的参数也是一个可迭代对象,长度必须为 5,示例:

    1
    2
    3
    from urllib.parse import urlunsplit  
    data = ['http', 'www.baidu.com', 'index.html', 'a=6', 'comment']
    print(urlunsplit(data))

    输出结果:

    1
    http://www.baidu.com/index.html?a=6#comment

    【1.4.8】 urllib.parse.urljoin()

    对 URL 进行组合,提供两个 URL 作为两个参数,将会自动分析 URL 的 scheme、netloc 和 path 这 3 个内容并对新链接缺失的部分进行补充,最后返回结果,示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from urllib.parse import urljoin  
    print(urljoin('http://www.baidu.com', 'friends.html'))
    print(urljoin('http://www.baidu.com', 'https://www.itrhx.com/friends.html'))
    print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html'))
    print(urljoin('http://www.baidu.com/friends.html', 'https://www.itrhx.com/friends.html?id=2'))
    print(urljoin('http://www.baidu.com?wd=trhx', 'https://www.itrhx.com/index.html'))
    print(urljoin('http://www.baidu.com', '?category=2#comment'))
    print(urljoin('www.baidu.com', '?category=2#comment'))
    print(urljoin('www.baidu.com#comment', '?category=2'))

    输出结果:

    1
    2
    3
    4
    5
    6
    7
    8
    http://www.baidu.com/friends.html
    https://www.itrhx.com/friends.html
    https://www.itrhx.com/friends.html
    https://www.itrhx.com/friends.html?id=2
    https://www.itrhx.com/index.html
    http://www.baidu.com?category=2#comment
    www.baidu.com?category=2#comment
    www.baidu.com?category=2

    【1.4.9】 urllib.parse.quote()

    将内容转化为 URL 编码的格式。当 URL 中带有中文参数时,可以将中文字符转化为 URL 编码,示例:

    1
    2
    3
    4
    from urllib.parse import quote
    keyword = '中国'
    url = 'https://www.baidu.com/s?wd=' + quote(keyword)
    print(url)

    输出结果:

    1
    https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD

    【1.4.10】 urllib.parse.unquote()

    与 quote() 方法相反,对 URL 进行解码,示例:

    1
    2
    3
    from urllib.parse import unquote  
    url = 'https://www.baidu.com/s?wd=%E4%B8%AD%E5%9B%BD'
    print(unquote(url))

    输出结果:

    1
    https://www.baidu.com/s?wd=中国

    【1.5】 urllib.robotparser 爬取权限判断

    【1.5.1】 Robots 协议简介

    Robots 协议即爬虫协议,用来告诉爬虫和搜索引擎哪些页面可以抓取,哪些不可以抓取。它通常是一个叫作 robots.txt 的文本文件,一般放在网站的根目录下。

    robots.txt 基本格式:

    1
    2
    3
    User-agent:
    Disallow:
    Allow:

    • User-agent 为搜索爬虫的名称,设置为 * 则表示对任何爬虫皆有效;
    • Disallow 指定了不允许抓取的目录,设置为 / 则代表不允许抓取所有页面;
    • Allow 指定了允许抓取的目录,一般和 Disallow 一起使用,一般不会单独使用,用来排除某些限制。

    一些常见的搜索爬虫名称及其对应的网站:

    爬虫名称网站名称网站地址
    BaiduSpider百度www.baidu.com
    Googlebot谷歌www.google.com
    360Spider360www.so.com
    Sogouspider搜狗www.sogou.com
    YodaoBot有道www.youdao.com
    Bingbot必应www.bing.com
    Yahoo! Slurp雅虎www.yahoo.com
    ia_archiverAlexawww.alexa.cn
    Scooteraltavistawww.altavista.com

    【1.5.2】 RobotFileParser 类常用方法

    RobotFileParser 类的声明:

    1
    urllib.robotparser.RobotFileParser(url='')

    常用方法及其解释:

    • set_url:用来设置 robots.txt 文件的链接。如果在创建 RobotFileParser
      对象时传入了链接,那么就不需要再用这种方法了。

    • read:读取 robots.txt 文件并进行分析。此方法执行一个读取和分析操作,若不调用此方法,接下来的判断都会为 False,这个方法不会返回任何内容,但是执行了读取操作。

    • parse:解析 robots.txt 文件,传入的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容。

    • can_fetch:该方法传入两个参数,第一个是 User-agent,第二个是要抓取的 URL。返回的内容是该搜索引擎是否可以抓取这个 URL,返回结果是 True 或 False。

    • mtime:返回的是上次抓取和分析 robots.txt 的时间,此方法可以定期检查来抓取最新的 robots.txt。

    • modified:将当前时间设置为上次抓取和分析 robots.txt 的时间。

    以简书为例:

    1
    2
    3
    4
    5
    6
    from urllib.robotparser import RobotFileParser
    rp = RobotFileParser()
    rp.set_url('http://www.jianshu.com/robots.txt')
    rp.read()
    print(rp.can_fetch('*', 'https://www.jianshu.com/p/6d9527300b4c'))
    print(rp.can_fetch('*', "http://www.jianshu.com/search?q=python&page=1&type=collections"))

    输出结果:

    1
    2
    False
    False

    ]]>
    - - - - - Python3 学习笔记 - - 爬虫学习 - - - - - - - 爬虫 - - urllib - - - -
    - - - - - 一个 JS 脚本实现网站预加载,提升页面加载速度 - - /2019/08/23/A24-instant.page/ - -

    instant.page 使用即时预加载技术,在用户点击之前预先加载页面。当用户的鼠标悬停在一个链接上超过 65 毫秒时,浏览器会对此页面进行预加载,当用户点击链接后,就从预加载的缓存中直接读取页面内容,从而达到缩短页面加载时间的目的。


    演示

    以我博客为例,使用了这项技术后,当鼠标在一个链接停留超过 65 毫秒时,Network 里可以看见相关文章已经预加载出来了,而停留时间过短就不会预加载(红色部分,状态为 canceled

    使用方法:
    将以下HTML代码放在</ body> 之前即可:

    1
    <script src="//instant.page/1.2.2" type="module" integrity="sha384-2xV8M5griQmzyiY3CDqh1dn4z3llDVqZDqzjzcY+jCBCk/a5fXJmuZ/40JJAPeoU"></script>

    但是此脚本是官方的,储存在国外服务器,对国内访问不太友好,可以将该JS脚本储存到自己的服务器上,点此获取该JS脚本,然后再根据以下格式在</ body> 之前引用:

    1
    <script src="`存放路径`/instantclick-1.2.2.js" type="module"></script>

    也可以直接使用我的,使用 jsDeliver CDN 加速,速度还可以:

    1
    <script src="https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.2/js/instantclick-1.2.2.js" type="module"></script>

    参考资料:《网站预加载 JS 脚本 instant.page》——by 左岸 ;instant.page官网

    ]]>
    - - - - - WEB前端 - - - - - - - instant.page - - JS 预加载 - - - -
    - - - - - 网站ICP备案和公安备案流程 - - /2019/08/23/A23-beian/ - -

    Website-Approve.png

    网站备案分为ICP备案和公安备案

    • ICP备案:ICP备案的目的就是为了防止在网上从事非法的网站经营活动,打击不良互联网信息的传播,如果网站不备案的话,很有可能被查处以后关停。根据中华人民共和国信息产业部第十二次部务会议审议通过的《非经营性互联网信息服务备案管理办法》条例,在中华人民共和国境内提供非经营性互联网信息服务,应当办理备案。未经备案,不得在中华人民共和国境内从事非经营性互联网信息服务。而对于没有备案的网站将予以罚款或关闭。

    • 公安备案:网站备案是根据国家法律法规需要网站的所有者向国家有关部门申请的备案,公安局备案是其中一种。公安局备案一般按照各地公安机关指定的地点和方式进行,操作流程会比ICP备案流程简单,主要是已登记为主。

    以百度官网为例,其中京公安网备11000002000001就是公安备案,京ICP证030173号就是ICP备案
    01.png

    – ICP备案

    一般在域名服务商那里都会有代备案系统,下面以阿里云为例,进入备案系统:
    02.png

    1、填写信息验证备案类型

    备案主办单位填写,个人就选个人,企业就选企业,按照实际信息填写:
    03.png

    2、产品验证

    对搭建备案网站的云服务器进行验证,如果你在阿里云购买了相关产品,就选择相应的产品类型和实例进行验证,也可以勾选已有备案服务号,填写服务号进行验证,备案服务号可以通过备案控制台进行申请,具体操作可以参考官方文档《申请备案服务号》,也有的小伙伴没有在任何地方购买过服务器等相关产品,比如单纯搭建一个 Github Pages + Hexo 轻量级的个人博客,这种博客没有后端,不需要服务器,但是要备案怎么办?这种情况也好解决,去某宝买一个服务号就行了。
    04.png
    05.png

    3、填写网站信息

    填写网站信息以及办理备案的个人或者单位的真实信息,在填写网站名称的时候要特别注意!特别注意!特别注意!不满足要求的话是会被打回的!不能使用姓名、地名、成语、不能包含公司、组织等企业性质的词语……具体要求可以参考官方文档《填写主体信息和网站信息》
    06.png
    07.png

    4、上传资料

    根据要求,上传证件照片或证件彩色扫描件。身份证好说,拍好了上传就行了,注意《网站备案信息真实性核验单》需要你下载并打印在一张A4纸上,使用黑色签字笔填写,不能涂改,具体可参照所给的示例进行填写,填写完成后再拍照上传。企业网站类似,提交备案后会在一个工作日内进行初审。
    08.png
    09.jpg
    10.png

    5、人脸核验或幕布拍照核验

    根据不同地域管局要求及核验平台的支持情况,使用人脸识别进行核验,或者申请专用幕布进行幕布拍照核验

    地区核验要求
    上海、福建地区用户需使用阿里云APP进行人脸核验。如果使用PC端发起的备案申请,请根据界面提示下载阿里云APP进行人脸核验。
    广东、辽宁、安徽、重庆地区用户首次备案、新增网站:支持使用阿里云APP进行人脸核验或通过阿里云备案平台(PC端)进行幕布拍照核验。
    其他备案类型:需通过阿里云备案平台(PC端)进行幕布拍照核验。
    其他地区用户通过阿里云备案平台(PC端)进行幕布拍照核验。

    以幕布拍照核验为例,如果你没有阿里云的幕布,就需要申请幕布(免费的),邮寄很快,大约两三天就到了,等收到幕布后,按照要求进行拍照,一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!一定要仔细阅读拍照说明!不合格依旧会被打回!拍照完成后上传即可。
    11.png
    12.png
    13.png

    6、提交管局、短信核验

    当照片审核通过后,就会提交到管局,工信部要求部分省市成为手机号码短信核验试点省市,相应省市的用户在阿里云备案平台提交备案申请且初审完成后,会收到工信部发送的核验短信,短信包含验证码和验证地址,需要在收到短信的24小时内完成短信核验,备案申请才能进入管局审核。
    需短信核验省份:

    • 2017年12月18日起:天津、甘肃、西藏、宁夏、海南、新疆、青海被列为试点省份。
    • 2018年9月10日起:浙江、四川、福建、陕西、重庆、广西、云南被列为试点省份。
    • 2018年9月24日起:山东、河南、安徽、湖南、山西、黑龙江、内蒙古、湖北被列为试点省份。

    14.png

    7、ICP备案完成

    整个备案过程中会有阿里云的客服打电话给你,进行信息确认,备案申请信息成功提交管局系统后,管局审核一般为 3 - 20 个工作日(亲测很快,不到一个周就通过了),审核通过后会收到阿里云的邮件通知。
    15.png

    – 公安备案

    公安备案个人觉得比ICP备案还要麻烦,自己在公安备案的时候,最开始申请了一个月也没给我处理(大概是地方原因,所在的市比较小,估计都没几个人办过网站,网警也不太负责),与ICP备案最大的不同,如果你是交互式网站的话,公安备案是需要你去公安机关当面审核的,这也是比较麻烦的一点。

    1、用户注册、登录

    登录全国互联网安全管理服务平台,选择联网备案登录,注册账号并登录
    16.png

    2、新办网站备案申请

    点击新办网站申请,按实填写网站开办主体,上传身份证正反照和手持身份证件照。
    17.png
    18.png

    3、填写网站基本信息

    按实填写网站基本信息,需要注意的地方:

    IP:IP地址为阿里云/腾讯云的公网IP地址,请不要填写内网IP。

    域名证书:以阿里云为例,进入【域名控制台】,点击域名后面的【管理】,选择【域名证书下载】即可,其它服务商类似。

    网络接入/域名注册服务商:若办理公安备案的域名是通过阿里云完成的工信部备案,则按照以下填写:
    网络接入服务商:

    • 接入商所属地区管辖:境内
    • 接入商所属区域 :浙江省 杭州市 滨江区
    • 名称:阿里云计算有限公司
    • 网站接入方式:租赁虚拟空间

    域名注册服务商:

    • 域名商所属地区管辖:境内
    • 域名服务商所属区域:浙江省 杭州市 余杭区
    • 名称:阿里云计算有限公司(原万网)

    也可以通过点击后面的查询网络接入\域名注册服务商直接选择相应服务商,其他服务商类似

    服务类型:交互式服务指:为互联网用户提供信息发布、交流互动等服务,包括但不限于论坛、博客、微博、网络购物、网上支付等服务类型,此项选择是否提供互联网交互服务将会直接影响到后面是否需要去公安局当面核验,若选择,当地网警会打电话叫你去公安局当面核验,还需要填写《交互式服务安全检查表》等各种文件,总之是比较麻烦的,个人小网站,博客什么的建议选择,选择www服务,这样的话不用去当面核验,审核下来也比较快,企业单位用户建议选择交互式。

    其他信息如实填写即可!
    19.png

    4、填写网站负责人信息

    填写网站安全负责人和网站应急联络人相关信息,网站应急联络人直接勾选同主体负责人后会自动填入。
    20.png

    5、同意责任书并提交审核

    《互联网信息服务单位网络安全责任告知书》有30秒的强制阅读时间,建议认真阅读一下告知书的内容。然后勾选我已阅读,点击提交即可。随后可以看到审核状态,不同地区政策有所不同,会有当地的网警联系网站负责人的,审核通过后记得在网站首页底部张贴公安机关核发的备案图标!
    21.png
    22.png

    ]]>
    - - - - - Hexo - - WEB前端 - - - - - - - ICP备案 - - 公安备案 - - - -
    - - - - - 恶意刷留言者——你是什么垃圾? - - /2019/08/23/A25-SB/ - -

    有一种动物,自认为自己技术了得,实则和CXK差不多,以攻击他人为乐,这种动物称为程序员中的垃圾,哦!不,这种动物称不上程序员!

    这个周连续被人刷垃圾评论,具体开始时间不记得了,不想多说什么,太多的文字用在垃圾身上简直是玷污中华上下五千年的文化,只问一句,你是什么垃圾?

    随便提一句,这家伙连我的情侣博客一起刷的,真是让人大跌眼镜啊,估计自己没女朋友吧,见不得别人好,悲催啊,不知道又是哪个学校,哪个公司,哪个家庭摊上了这种垃圾。


    01

    02

    请问你是什么垃圾?

    垃圾分类,从我做起!
    ]]>
    - - - - - BLOG - - - - - - - 垃圾 - - - -
    - - - - - 利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS - - /2019/08/11/A28-hexo-add-https/ - -

    利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS


    HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。

    HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。


    目前大多数基于 GitHub Pages 的 Hexo 博客都是利用 CloudFlare 的 CDN 中转来启用 HTTPS 的,实现方法可以参考我的文章:《利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持》,这样的做法确实可以起到开启HTTPS的目的,但是这样做也有弊端,你会发现 CDN 中转,国外访问的话,可以起到加速的作用,但是国内访问反而速度降低了,还不如直接连接GitHub呢

    其实 GitHub 官方是支持自定义域名开启 HTTPS 的,之前我和大多数人一样,以为只有 GitHub Pages 自带的域名(xxx.github.io)才能开启 HTTPS,直到有一天我发现了官方在2018年5月1日发表的博客:《Custom domains on GitHub Pages gain support for HTTPS》,大概讲的意思就是从8月份开始, GitHub Pages 上的自定义域名也能开启 HTTPS 了,下面就具体介绍一下如何实现

    如果你以前域名的记录类型是 CNAME 方式,那么就不需要做任何更改
    如果你以前域名的记录类型是 A 方式,那么就需要把记录值指向以下IP地址:

    • 185.199.108.153
    • 185.199.109.153
    • 185.199.110.153
    • 185.199.111.153

    修改好记录值后,我们需要再次来到你博客的 GitHub 仓库,在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】,注意,如果此时你不能勾选,请删除【Custom domain】里面你的域名并点击【Save】保存,刷新网页后就可以勾选了,然后在把域名填进去并保存即可,短时间可能会出现不安全的提示,这是因为加密证书大概一个小时左右才会生效,等一会儿就好了


    01

    最后贴一个我的域名解析,可作为参考:


    02
    ]]>
    - - - - - Hexo - - - - - - - Hexo - - HTTPS - - - -
    - - - - - Github+jsDelivr+PicGo 打造稳定快速、高效免费图床 - - /2019/08/01/A27-image-hosting/ - -
    ImgHosting

    – 前言

    图床是个啥东西就不用过多介绍了,先来对比一下各路图床:

    • 微博图床:以前用的人比较多,从2019年4月开始开启了防盗链,凉凉
    • SM.MS:运营四年多了,也变得越来越慢了,到了晚上直接打不开图片,速度堪忧
    • 其他小众图床:随时有挂掉的风险
    • Imgur等国外图床:国内访问速度太慢,随时有被墙的风险
    • 大厂储存服务:例如七牛云、又拍云、腾讯云COS、阿里云OSS等,容量限制,操作繁琐,又是实名认证又是域名备案的,麻烦,而且还要花钱(有钱又不怕麻烦的当我没说)

    因此,GitHub 图床是个不错的选择,利用 jsDelivr CDN 加速访问(jsDelivr 是一个免费开源的 CDN 解决方案),PicGo 工具一键上传,操作简单高效,GitHub 和 jsDelivr 都是大厂,不用担心跑路问题,不用担心速度和容量问题,而且完全免费,可以说是目前免费图床的最佳解决方案!


    – 新建GitHub仓库

    登录/注册GitHub,新建一个仓库,填写好仓库名,仓库描述,根据需求选择是否为仓库初始化一个README.md描述文件


    01


    02

    – 生成一个Token

    在主页依次选择【Settings】-【Developer settings】-【Personal access tokens】-【Generate new token】,填写好描述,勾选【repo】,然后点击【Generate token】生成一个Token,注意这个Token只会显示一次,自己先保存下来,或者等后面配置好PicGo后再关闭此网页


    03


    04


    05


    06


    07

    – 配置PicGo

    前往下载PicGo,安装好后开始配置图床


    08

    关于 jsDelivr 具体是如何引用资源的可以参考我的另一篇博客:《免费CDN:jsDelivr+Github》


    – 进行高效创作

    配置好PicGo后,我们就可以进行高效创作了,将图片拖拽到上传区,将会自动上传并复制访问链接,将链接粘贴到博文中就行了,访问速度杠杠的,此外PicGo还有相册功能,可以对已上传的图片进行删除,修改链接等快捷操作,PicGo还可以生成不同格式的链接、支持批量上传、快捷键上传、自定义链接格式、上传前重命名等,更多功能自己去探索吧!

    ]]>
    - - - - - 图床 - - - - - - - jsDelivr - - 图床 - - PicGo - - - -
    - - - - - 利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持 - - /2019/07/31/A26-hexo-add-https/ - -

    利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持


    HTTP(超文本传输协议),是一个基于请求与响应,无状态的,应用层的协议,常基于TCP/IP协议传输数据,互联网上应用最为广泛的一种网络协议,所有的WWW文件都必须遵守这个标准。设计HTTP的初衷是为了提供一种发布和接收HTML页面的方法。

    HTTPS(超文本传输安全协议),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。


    – 前言

    GitHub Pages 自带的域名(xxx.github.io)支持开启 https 服务,可以在仓库的【Settings】- 【GitHub Pages】下勾选【Enforce HTTPS】即可,但是如果你设置了自定义域名的话,就比较复杂了,因为 hexo 博客是托管在 GitHub 上的,没有自己的服务器,因此也不支持上传 SSL 证书,从2018年5月1日起,GitHub官方也支持自定义域名开启https了,实现方法可参考我的文章:《利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS》,另外一种方法就是利用 Cloudflare 的 CDN 中转来启用 HTTPS,这种方法的弊端就是国内访问速度可能会变慢,本文主要讲述这种方法

    Cloudflare 是一家美国的跨国科技企业,以向客户提供网站安全管理、性能优化及相关的技术支持为主要业务,它提供了免费的 https 服务,注意不是应用SSL证书,实现原理:用户到CDN服务器的连接为 https 方式,而CDN服务器到 GithubPages 服务器的连接为 http 方式,在CDN服务器那里加上反向代理

    – 注册 Cloudflare

    Cloudflare官网 注册账号


    01.png

    – 添加站点

    添加你的站点,一直下一步即可


    02.png


    03.png

    如果你已经在域名服务商那里解析过域名的话,之后就会出现你域名的解析列表,如果还没有解析过,可以参考《为hexo博客配置个性域名》

    04.png

    –修改DNS

    点击下一步 Cloudflare 会提供给你两个 DNS 地址


    05.png

    到域名服务商那里修改DNS,以阿里云为例,依次选择【控制台】-【域名】,选择你的域名,点击【管理】-【修改DNS】,将上面 Cloudflare 提供的两个 DNS 地址填进去,会过几分钟才生效

    08.png


    09.png


    10.png

    –开启 HTTPS

    在 Cloudflare 管理页面,点击【Crypto】选项,选择 SSL 的模式为【full】,注意:在CloudFlare 上激活站点后,可能需要24小时才能颁发新证书,耐心等待即可


    07.png

    关于三种模式 Flexible、Full、Full (Strict) 的区别:

    • Flexible:访客与 Cloudflare 之间是加密的,Cloudflare 到站点服务器是不加密的

    • Full:访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它不会验证你服务器上的证书是否合法,因此你可以在你服务器上安装任何证书,包括自签名证书

    • Full (strict):访客到 Cloudflare、Cloudflare 到站点服务器都是加密的,它会验证你服务器上的证书是否合法,你必须在你的服务器上安装有可信赖的CA证书,并且这个证书必须是未过期,包含有域名等信息的

    至此,我们的域名就支持 https 访问了,但是当用户输入 http://xxxxxx 访问时,浏览器依旧会以 http 协议来访问,并不会跳转到 https,这时候就需要利用重定向来解决了

    –重定向强制 HTTPS

    Cloudflare 提供了一个名叫 Page Rules 的页面规则的功能,我们可以利用此功能对 URL 做一些处理,当用户访问是 HTTP 的时候重定向到 HTTPS,点击【Page Rules】选项,点击【Create Page Rules】,新建如下规则并保存即可


    06.png

    现在我们的 Hexo 博客就实现了全站 HTTPS!

    ]]>
    - - - - - Hexo - - - - - - - Hexo - - HTTPS - - - -
    - - - - - Eclipse 通过 JDBC 连接 SQL Server - - /2019/05/14/A22-eclipse-connects-to-sql/ - -

    本文用到的软件版本以及相关环境:

    Eclipse Photon Release (4.8.0)
    JDK-10.0.2
    SQL Server 2012

    1.配置 SQL Server 2012

    打开 SQL Server Management Studio,使用 SQL Server 身份验证 登录:

    001.png

    如果在安装 SQL Server 2012 时选用了Windows身份验证登录方式,则需要重新设置,设置方法参考:《SQL Server 登录更换【Windows身份验证】为【SQL Server 身份验证】》

    登录成功后,打开 SQL Server 配置管理器:

    002.png

    在左边找到 SQL Server 网络配置,点击【你的数据库名】的协议,将右边栏的 Shared Memory、Named Pipes、TCP/IP 全部右键选择启用

    003.png
    004.png

    双击 TCP/IP(或者右键选择属性),选择【IP地址】,将【IP1】和【IP10】的【IP地址】设为 127.0.0.1

    005.png

    将所有【IPx】(IP1、IP10、IP11、IP12等)的【已启用】设为

    006.png

    下拉到窗口底部,将 【IPAll】 中的【TCP端口】设成 1433,其余不变

    007.png

    2.开启 Telnet 服务

    打开【控制面板】,选择【程序】,点击【启用或关闭 Windows 功能】,找到【Telnet Client】勾选并保存,Windows 7 或者以下的版本则勾选【Telnet 服务器】和【Telnet 客户端

    008.png
    009.png

    3.测试1433端口是否打开

    运行cmd,输入 telnet 127.0.0.1 1433,若提示连接失败,则说明1433端口没有打开,需要重新进行以上配置,若连接成功,则显示如下:

    010.png

    4.下载JDBC

    点击此处下载各个版本JDBC,不同版本的JDBC驱动程序适用的JAR不同,与不同版本的SQL兼容性也不同,具体参考《Microsoft SQL Server JDBC 驱动程序支持矩阵》,比如使用 SQL Server 2012 我们可以下载6.0的版本,下载sqljdbc_6.0.8112.200_chs.tar.gz文件,解压后可以找到sqljdbc41.jarsqljdbc42.jar文件,使用时要注意自己JDK是哪个版本的,1.80以上的则对应 sqljdbc42.jar 类库

    5.Eclipse 连接 SQL Server

    将 sqljdbc41.jar 或者 sqljdbc42.jar 放到一个文件夹下,打开 Eclipse,在需要连接数据库的项目里,右键【src】,选择【Build Path】、【Configure Build Path…】,在弹出的窗口选择【Libraries】,选择【Modulepath】,单击【Add External JARs…】,找到下载的 sqljdbc41.jar 或者 sqljdbc42.jar 文件并打开,然后【Apply and Close】保存

    011.png
    012.png
    013.png

    6.测试连接

    打开 SQL Server 2012,在其中新建数据库 test

    014.png

    Eclipse中,在项目下新建一个package,再新建一个class,用于测试数据库的连接:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package test;
    import java.sql.*;
    public class Main {
    public static void main(String [] args)
    {
    String driverName="com.microsoft.sqlserver.jdbc.SQLServerDriver";
    String dbURL="jdbc:sqlserver://localhost:1433;DatabaseName=test"; //要连接的数据库名
    String userName="sa"; //数据库用户名
    String userPwd="000000"; //数据库密码
    try
    {
    Class.forName(driverName);
    Connection dbConn=DriverManager.getConnection(dbURL,userName,userPwd);
    System.out.println("连接数据库成功");
    }
    catch(Exception e)
    {
    e.printStackTrace();
    System.out.print("连接失败");
    }
    }
    }

    如果以上所有操作正确,就能成功连接数据库了:

    015.png

    ]]>
    - - - - - Java - - - - - - - JDBC - - SQL Server 2012 - - Elicpse - - - -
    - - - - - Python PEP8 代码规范常见问题及解决方法 - - /2019/04/15/A21-PEP8/ - -

    之前一直用 Python IDLE 写代码,最近换成 PyCharm 写代码总是会出现波浪号,这才了解到 Python 的 PEP8 代码规范,所以将常见的 PEP8 代码规范问题和解决方法记录一下,学习一下,遇到了再持续更新,养成良好的习惯,编写规范的代码!


    • PEP 8: no newline at end of file
      解决方法:代码末尾需要另起一行,光标移到最后回车即可

    • PEP 8: indentation is not a multiple of four
      解决方法:缩进不是4的倍数,检查缩进

    • PEP 8: over-indented
      解决方法:过度缩进,检查缩进

    • PEP 8: missing whitespace after’,’
      解决方法:逗号后面少了空格,添加空格即可,类似还有分号或者冒号后面少了空格

    • PEP 8: multiple imports on one line
      解决方法:不要在一句 import 中引用多个库,举例:import socket, urllib.error最好写成:import socket import urllib.error

    • PEP 8: blank line at end of line
      解决方法:代码末尾行多了空格,删除空格即可

    • PEP 8: at least two spaces before inline comment
      解决方法:代码与注释之间至少要有两个空格

    • PEP 8: block comment should start with ‘#’
      解决方法:注释要以#加一个空格开始

    • PEP 8: inline comment should start with ‘#’
      解决方法:注释要以#加一个空格开始

    • PEP 8: module level import not at top of file
      解决方法:import不在文件的最上面,可能之前还有其它代码

    • PEP 8: expected 2 blank lines,found 0
      解决方法:需要两条空白行,添加两个空白行即可

    • PEP 8: function name should be lowercase
      解决方法:函数名改成小写即可

    • PEP 8: missing whitespace around operator
      解决方法:操作符(’=’、’>’、’<’等)前后缺少空格,加上即可

    • PEP 8: unexpected spaces around keyword / parameter equals
      解决方法:关键字/参数等号周围出现意外空格,去掉空格即可

    • PEP 8: multiple statements on one line (colon)
      解决方法:多行语句写到一行了,比如:if x == 2: print('OK')要分成两行写

    • PEP 8: line too long (82 > 79 characters)
      解决方法:超过了每行的最大长度限制79


    如果想要选择性忽略PEP8代码风格的警告信息可以使用以下方法:(养成良好的习惯,编写规范的代码!不推荐忽略!)

    ①将鼠标移到出现警告信息的地方,按 alt+Enter,选择忽略(Ignore)这个错误即可:
    01
    ②依次选择 File - Settings - Editor - Inspections,在 Python下找到 PEP8 coding style violation 选项,在右下角的 Ignore errors 里点击加号可以添加需要忽略的警告信息ID(ID信息见后面附录),例如想要忽略indentation contains mixed spaces and tabs这个警告,只需要添加其ID:E101 即可
    02
    附录:全部警告信息以及对应的ID,官方地址:https://pep8.readthedocs.io/en/latest/intro.html#error-codes

    codesample message
    E1Indentation
    E101indentation contains mixed spaces and tabs
    E111indentation is not a multiple of four
    E112expected an indented block
    E113unexpected indentation
    E114indentation is not a multiple of four (comment)
    E115expected an indented block (comment)
    E116unexpected indentation (comment)
    E117over-indented
    E121 (*^)continuation line under-indented for hanging indent
    E122 (^)continuation line missing indentation or outdented
    E123 (*)closing bracket does not match indentation of opening bracket’s line
    E124 (^)closing bracket does not match visual indentation
    E125 (^)continuation line with same indent as next logical line
    E126 (*^)continuation line over-indented for hanging indent
    E127 (^)continuation line over-indented for visual indent
    E128 (^)continuation line under-indented for visual indent
    E129 (^)visually indented line with same indent as next logical line
    E131 (^)continuation line unaligned for hanging indent
    E133 (*)closing bracket is missing indentation
    E2Whitespace
    E201whitespace after ‘(‘
    E202whitespace before ‘)’
    E203whitespace before ‘:’
    E211whitespace before ‘(‘
    E221multiple spaces before operator
    E222multiple spaces after operator
    E223tab before operator
    E224tab after operator
    E225missing whitespace around operator
    E226 (*)missing whitespace around arithmetic operator
    E227missing whitespace around bitwise or shift operator
    E228missing whitespace around modulo operator
    E231missing whitespace after ‘,’, ‘;’, or ‘:’
    E241 (*)multiple spaces after ‘,’
    E242 (*)tab after ‘,’
    E251unexpected spaces around keyword / parameter equals
    E261at least two spaces before inline comment
    E262inline comment should start with ‘# ‘
    E265block comment should start with ‘# ‘
    E266too many leading ‘#’ for block comment
    E271multiple spaces after keyword
    E272multiple spaces before keyword
    E273tab after keyword
    E274tab before keyword
    E275missing whitespace after keyword
    E3Blank line
    E301expected 1 blank line, found 0
    E302expected 2 blank lines, found 0
    E303too many blank lines (3)
    E304blank lines found after function decorator
    E305expected 2 blank lines after end of function or class
    E306expected 1 blank line before a nested definition
    E4Import
    E401multiple imports on one line
    E402module level import not at top of file
    E5Line length
    E501 (^)line too long (82 > 79 characters)
    E502the backslash is redundant between brackets
    E7Statement
    E701multiple statements on one line (colon)
    E702multiple statements on one line (semicolon)
    E703statement ends with a semicolon
    E704 (*)multiple statements on one line (def)
    E711 (^)comparison to None should be ‘if cond is None:’
    E712 (^)comparison to True should be ‘if cond is True:’ or ‘if cond:’
    E713test for membership should be ‘not in’
    E714test for object identity should be ‘is not’
    E721 (^)do not compare types, use ‘isinstance()’
    E722do not use bare except, specify exception instead
    E731do not assign a lambda expression, use a def
    E741do not use variables named ‘l’, ‘O’, or ‘I’
    E742do not define classes named ‘l’, ‘O’, or ‘I’
    E743do not define functions named ‘l’, ‘O’, or ‘I’
    E9Runtime
    E901SyntaxError or IndentationError
    E902IOError
    W1Indentation warning
    W191indentation contains tabs
    W2Whitespace warning
    W291trailing whitespace
    W292no newline at end of file
    W293blank line contains whitespace
    W3Blank line warning
    W391blank line at end of file
    W5Line break warning
    W503 (*)line break before binary operator
    W504 (*)line break after binary operator
    W505 (*^)doc line too long (82 > 79 characters)
    W6Deprecation warning
    W601.has_key() is deprecated, use ‘in’
    W602deprecated form of raising exception
    W603‘<>’ is deprecated, use ‘!=’
    W604backticks are deprecated, use ‘repr()’
    W605invalid escape sequence ‘x’
    W606‘async’ and ‘await’ are reserved keywords starting with Python 3.7
    ]]>
    - - - - - Python3 学习笔记 - - 学习经验 - - - - - - - Python - - PEP8 - - - -
    - - - - - VMware Pro 15 安装 Deepin15.9 国产操作系统 - - /2019/04/14/A20-install-deepin15.9/ - -

    Deepin是由武汉深之度科技有限公司开发的Linux发行版,个人认为其界面设计非常美观,而且作为国产操作系统,值得我们去体验和支持!


    1.下载安装 VMware Workstation Pro 15

    进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装
    001

    2.下载 Deepin15.9 系统

    进入 deepin 官网,下载最新版 deepin 系统镜像
    002

    3.在 VMware 中创建虚拟机

    打开安装好的 VMware Workstation Pro 15,选择创建新的虚拟机
    003
    在新建虚拟机向导中选择自定义(高级):
    004
    默认直接下一步,直到出现下图,再选择稍后安装操作系统:
    005
    选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本:
    006
    更改虚拟机名称及存放位置:
    007
    接下来为虚拟机指定处理器数量、分配内存(太大了可能会导致卡顿,太小了也不好,推荐内存大小即可)一直选择默认即可,选择磁盘时,选择创建新虚拟磁盘:
    008
    选择将虚拟磁盘储存为单个文件:
    009
    默认下一步:
    010
    点击完成:
    011
    此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Deepin,单击 Deepin,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Deepin 15.9 镜像文件,点击“确定”
    012
    013

    4.在虚拟机上安装 Deepin 系统

    单击 Deepin,选择“开启此虚拟机”
    014
    接下来就是选择语言、创建用户、选择时区、指定磁盘等过程:
    015
    016
    017
    018
    安装完成后:
    019
    可以看见界面还是相当美观的,系统也自带了深度的一些软件,比如深度录屏,深度录音,深度影院,深度计算器等等的一些小工具,作为国产操作系统,个人觉得已经非常优秀了,值得去体验!

    ]]>
    - - - - - Linux - - - - - - - VMware - - Deepin - - - -
    - - - - - Windows 系统中 Pygame 的安装 - - /2019/03/10/A19-install-pygame/ - -

    Pygame是跨平台Python模块,专为电子游戏设计,可用于管理图形、动画乃至声音,建立在SDL基础上,允许实时电子游戏研发而无需被低级语言(如机器语言和汇编语言)束缚,通过使用Pygame来处理在屏幕上绘制图像等任务,你不用考虑众多繁琐而艰难的编码工作,而是将重点放在程序的高级逻辑上。

    你可以从以下三个地址查找与你运行的Python版本相匹配的Windows安装程序:

    01

    如果下载的是.exe文件,直接运行它,如果下载的是.whl文件,就需要打开命令窗口,切换到该文件所在的目录,使用pip来运行它:

    首先检查电脑是否安装了pip,打开终端窗口,执行如下命令:

    1
    >python -m pip --version

    如果输出版本信息则已安装:

    1
    >pip 18.1 from E:\Python\lib\site-packages\pip (python 3.6)

    否则请安装pip,访问 https://bootstrap.pypa.io/get-pip.py ,如果出现对话框请直接保存文件,如果出现的是get-pip.py的源代码,则需要新建一个get-pip.py文件,将该代码复制粘贴到其中,使用下面的命令运行get-pip.py:

    1
    >python get-pip.py

    安装完成后可再次使用python -m pip --version命令检查是否成功安装了pip,成功安装pip后,使用以下命令来安装Pygame:(注意要先cd到你下载的文件的目录)

    1
    >python -m pip install --user 下载的.whl文件名

    出现以下信息则表示安装成功:

    1
    >Successfully installed 你安装的Pygame版本

    比如我的Python版本是3.6.5,64位的,则需要下载pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl,该文件保存到了桌面,使用下面的命令安装Pygame:

    1
    2
    3
    4
    5
    6
    7
    8
    C:\Users\Lenovo>cd desktop

    C:\Users\Lenovo\Desktop>python -m pip install --user pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl
    Processing c:\users\lenovo\desktop\pygame‑1.9.4‑cp36‑cp36m‑win_amd64.whl
    Installing collected packages: pygame
    Successfully installed pygame‑1.9.4

    C:\Users\Lenovo>Desktop>

    检查是否成功安装Pygame:在Python的IDLE里输入import pygame,如果不报错,则安装成功,再输入pygame.ver就能看到版本号:

    02
    )

    可能出现的问题:
    报错:xxxxxxxxxxxxxxxxxxxxxx.whl is not a supported wheel on this platform.
    原因:Python版本与Pygame版本不对应
    解决方法:Pygame文件名中的cp**表示Python对应的版本,另外并不是你电脑64位则下载64位,要看你安装的Python是否为64位,注意下载对应的版本!

    报错:You are using pip version x.x.x, however version x.x.x is available.You should consider upgrading via the 'python -m pip install --upgrade pip' command.
    原因:版本需要更新
    解决方法:输入python -m pip install --upgrade pip命令进行更新即可

    Pygame安装完成后我们就可以使用Python来开发游戏啦!

    ]]>
    - - - - - Python3 学习笔记 - - 学习经验 - - - - - - - Pygame - - Python - - - -
    - - - - - 免费CDN:jsDelivr + Github - - /2019/02/10/A18-free-cdn/ - -

    本文有参考《jsDelivr+github使用教程,免费好用的cdn》—— By hojun


    CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。——百度百科

    放在Github的资源在国内加载速度比较慢,因此需要使用CDN加速来优化网站打开速度,jsDelivr + Github便是免费且好用的CDN,非常适合博客网站使用。


    1、新建Github仓库

    01

    2、克隆Github仓库到本地

    点击 Clone or download,一键复制仓库地址
    04

    在本地目录右键 Git Bash Here,执行以下命令:

    1
    git clone 一键复制的仓库地址

    3、上传资源

    复制需要上传的资源到本地git仓库(注:jsDelivr不支持加载超过20M的资源),在本地git仓库目录下右键 Git Bash Here,执行以下命令:

    1
    2
    3
    4
    git status                    //查看状态
    git add . //添加所有文件到暂存区
    git commit -m '第一次提交' //把文件提交到仓库
    git push //推送至远程仓库

    4、发布仓库

    点击release发布
    02

    自定义发布版本号
    03

    5、通过jsDelivr引用资源

    使用方法:https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径
    例如:https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@1.0/images/trhx.png
        https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.0.1/css/style.css
        https://cdn.jsdelivr.net/gh/moezx/cdn@3.1.3//The%20Pet%20Girl%20of%20Sakurasou.mp4

    注意:版本号不是必需的,是为了区分新旧资源,如果不使用版本号,将会直接引用最新资源,除此之外还可以使用某个范围内的版本,查看所有资源等,具体使用方法如下:

    ]]>
    - - - - - CDN - - - - - - - jsDelivr - - CDN - - - -
    - - - - - 新年快乐! - - /2019/02/05/A17-happy-new-year/ - -
    Cprintf("2019,祝大家");
    C++cout<<"一帆风顺";
    C#System.Console.WriteLine("二龙腾飞")
    VBMsg("三羊开泰")
    VCMessageBox("四季平安");
    JavaSystem.out.println("五福临门");
    JavaScriptalert("六六大顺")
    PHPecho "七星高照";
    Pythonprint("八方来财")
    Html<br/>九运当头<br/>
    ObjectivecNSLog(@"十全十美");
    QBasicPrint "阖家幸福"
    AspResponse.Write "心想事成"
    Rubyputs "财源广进"
    VBScriptMsgBox "幸福安康"
    XML<TextView android:text="大展宏图" />
    LUAprint("学业有成")
    DelphiShowMessage('万事如意');
    shellecho 步步高升
    perlprint '鸿案齐眉'
    LISP(format t "身体健康~%")
    powerBuildermessagebox("龙马精神")
    COBOLDISPLAY '笑口常开'
    aswingJOptionPane.showMessageDialog("happy","好运连连")
    AndroidToast.makeText(getApplicationContext(),"年年有余",Toast.LENGTH_SHORT).show()
    flexAlert.show("大吉大利");
    Foxpro?[家庭幸福!]
    iapptw("瑞气盈门")
    DOS批处理echo 鹏程万里
    易语言调试输出(“万事亨通”)
    Clojure(println "年年有今昔")
    verilog/systemverilog/e$display("岁岁有今朝")
    astrace("祝大家新年快乐!");
    ]]>
    - - - - - BLOG - - - - - - - BLOG - - - -
    - - - - - 一台电脑使用两个/多个GitHub账号部署两个/多个Hexo博客 - - /2019/01/18/A16-deploy-two-or-more-hexo-blogs/ - -

    01

    由于个人原因需要在一台电脑上部署两个Hexo博客,本来以为挺简单,没想到问题重重,首先是一个GitHub账号只能搭建一个Hexo博客,因此就需要使用其他GitHub账号;其次是一台电脑绑定两个GitHub账号,则需要两对公钥,在处理第二个问题时遇到的问题比较多,因为对这方面一窍不通,还是小白,所以折腾了一下午才解决,网上好多教程我都看不懂,觉得不(自)够(己)详(太)细(笨),因此详细记录一下

    原理分析:

    • SSH的公钥是GitHub作为本地仓库和远程仓库连接的唯一标识,一个公钥只能对应一个GitHub账户,如果将一个相同的公钥上传到不同的GitHub账户,GitHub则无法做出辨识,进而导致错误
    • 一台电脑,可以生成多对公私钥,可以通过配置,将不同的公钥上传到不同的GitHub账号,那么就不存在单个公钥绑定多个GitHub账号的情况存在了

    相关问题报错:

    • 同一台电脑部署第二个Hexo博客执行hexo g -d时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx.
    • 添加新的 SSH 密钥 到 SSH agent 执行ssh-add xxx时报错:Could not open a connection to your authentication agent.
    • 单独设置用户名/邮箱时报错:fatal: not in a git directory

    以下是详细过程:
    前提:假设你的第二个博客相关配置操作已经顺利完成,但使用hexo g -d命令部署到 GitHub 上时报错:ERROR: Permission to xxxxxx/xxxxxx.github.io.git denied to xxxxxx.

    - 查看当前密钥

    首先我们打开终端输入ls ~/.ssh/可以查看当前已有的密钥,显示id_rsaid_rsa_pub说明已经有一对密钥

    - 创建新的密钥

    首先使用以下命令进入 SSH根目录下:

    1
    cd ~/.ssh/

    方法一

    直接使用以下命令创建新密钥,然后两次回车即可:

    1
    ssh-keygen -t rsa -f  ~/.ssh/这里是新密钥名称 -C "这里是你的邮箱"

    注意区别新密钥名称和旧密钥名称,不要相同!!!

    方法二

    使用下面命令行创建新密钥:

    1
    ssh-keygen -t rsa -C "这里是你的邮箱"

    回车后会出现:

    1
    2
    Generating public/private rsa key pair.  
    Enter file in which to save the key (/c/Users/you/.ssh/id_rsa):

    注意此时需要你输入新密钥的名称,同样要注意区别新密钥名称和旧密钥名称,不要相同!!!之后再两次回车,新密钥创建完毕!

    - 配置config

    查看你的.ssh/根路径下, 有没有config文件,( 比如我的路径为C:\Users\Lenovo.ssh)没有则使用以下命令创建一个config文件:

    1
    touch config

    用记事本或者其他工具打开config文件(注意config文件是没有任何后缀名的),写入以下配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #第一个账号,默认使用的账号,不用做任何更改
    Host github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/id_rsa

    #第二个新账号,#"xxxxxx"为前缀名,可以任意设置,要记住,后面需要用到
    Host xxxxxx.github.com
    HostName github.com
    User git
    IdentityFile ~/.ssh/这里是你创建的新密钥的名称

    - 设置新GitHub账户SSH key

    输入以下命令复制你创建的公钥:

    1
    clip < ~/.ssh/这里是你创建的新密钥的名称.pub

    也可以直接在.ssh目录下找到你创建的新的公钥,文件名为新密钥的名称.pub,(比如我的是trhx_rsa.pub),用记事本打开,复制里面的内容,然后打开你的新GitHub账号主页,依次进入Settings —> SSH and GPG keys —> New SSH key,将刚复制的内容粘贴到Key那里,Title可以随便填,点击Add Key保存。

    - 清空本地的 SSH 缓存,添加新的 SSH 密钥 到 SSH agent中

    使用命令cd ~/.sshcd到.ssh根目录下,依次执行以下命令:

    1
    2
    3
    ssh-add -D
    ssh-add xxxxxx #旧密钥名称,一般是id_rsa
    ssh-add xxxxxx #新创建的密钥名称

    如果执行以上命令出现错误:Could not open a connection to your authentication agent.,那么就需要先执行ssh-agent bash,再执行以上命令

    - 验证配置是否成功

    依次执行以下命令,第一个为默认ssh_key验证;第二个为新的ssh_key验证,其中“xxxxxx”为你先前在config文件中的命名

    1
    2
    ssh -T git@github.com
    ssh -T git@xxxxxxx.github.com

    依次显示以下信息, 则说明配置成功:

    1
    Hi 你的用户名! You've successfully authenticated, but GitHub does not provide shell access.

    - 取消全局用户名/邮箱配置,单独设置用户名/邮箱

    执行如下命令,取消全局用户名和邮箱配置(如果已经设置了全局的话):

    1
    2
    git config --global --unset user.name
    git config --global --unset user.email

    分别进入你的两个Hexo博客.git目录下执行以下命令单独设置用户名/邮箱:

    1
    2
    git config user.name "这里是用户名"
    git config user.email "这里是你的邮箱"

    如果此时报错:fatal: not in a git directory,说明你没有进入.git目录下,具体路径:\Hexo\.deploy_git\.git,.git目录是隐藏的,需要你设置隐藏目录可见

    执行以下命令可以查看设置是否成功

    1
    git config --list

    - hexo 配置文件修改git地址

    打开你的第二个博客Hexo目录下的_config.yml文件,找到deploy关键字,写入以下配置并保存:

    1
    2
    3
    4
    deploy:
    type: git
    repository: git@xxxxxx.github.com:你的用户名/你的用户名.github.io.git
    branch: master

    比如我的配置:

    1
    2
    3
    4
    deploy:
    type: git
    repository: git@love109.github.com:love109/love109.github.io.git
    branch: master

    大功告成,再次执行hexo g -d就能成功将新的博客部署到 Github 上了

    ]]>
    - - - - - Hexo - - - - - - - Hexo - - Github - - - -
    - - - - - Python3 基础学习笔记 C09 - - /2018/11/16/A15-Python3-basic-C09/ - -
    Python3 基础学习笔记第九章 —— 【文件和异常】

    - 9.1 从文件中读取数据

    - 9.1.1 读取整个文件

    有一个文件,包含精确到小数点后30位的圆周率值,且在小数点后每10位处都换行:

    1
    2
    3
    4
    5
    Circumference rate.txt
    ----------
    3.1415926535
    8979323846
    2643383279

    以下两个程序将打开并读取这个文件,再将其内容显示到屏幕上:

    1
    2
    3
    4
    5
    #file_reader.py

    with open('Circumference rate.txt') as file_object:
    contents = file_object.read()
    print(contents)

    1
    2
    3
    4
    5
    #file_reader2.py

    contents = open ('Circumference rate.txt')
    print(contents.read())
    contents.close()

    函数open()接受一个参数:要打开的文件的名称,Python在当前执行的文件所在的目录中查找指定的文件;关键字with在不再需要访问文件后将其关闭;也可以调用open()close()来打开和关闭文件,如果使用这种方法,当程序存在bug时,close()语句未执行,文件将不会被关闭;方法read()将读取这个文件的全部内容,并将其作为一个长长的字符串储存在变量contents中,通过打印contents的值,就可以将这个文本文件的全部内容打印出来:

    1
    2
    3
    3.1415926535
    8979323846
    2643383279

    输出结果末尾有一空行,这是因为read()到达末尾时返回一个空字符串,而将这个空字符串显示出来就是一个空行,如果要删除末尾的空行,可在print语句中使用rstrip():

    1
    2
    3
    4
    5
    #file_reader.py

    with open('Circumference rate.txt') as file_object:
    contents = file_object.read()
    print(contents.rstrip())

    输出结果如下:

    1
    2
    3
    3.1415926535
    8979323846
    2643383279

    - 9.1.2 文件路径

    相对文件路径:假定程序文件位于python_work文件夹中,程序文件操作的文本文件位于python_work文件夹的子文件夹text_files中,此时可以使用相对文件路径来打开该文本文件,相对文件路径让Python到指定的位置去查找,而该位置是相对于当前运行的程序所在目录的

    在Linux和OS X中,相对路径类似于如下:

    1
    with open('text_files/filename.txt') as file_object:

    在Windows系统中,文件路径中使用反斜杠(\)而不是斜杠(/):

    1
    with open('text_files\filename.txt') as file_object:

    绝对文件路径:不用关心当前运行的程序储存在什么地方,直接将文件在计算机中的准确位置告诉Python,这称为绝对文件路径,绝对路径通常比相对路径更长,因此将其储存在一个变量中,再将变量传递给open()会有所帮助

    在Linux和OS X中,绝对路径类似于如下:

    1
    2
    file_path = '/home/ehmatthes/other_files/text_files/filename.txt'
    with open(file_path) as file_object:

    在Windows系统中,绝对路径类似于如下:

    1
    2
    file_path = 'C:\Users\ehmatthes\other_files\text_files\filename.txt'
    with open(file_path) as file_object:

    - 9.1.3 逐行读取

    要以每次一行的方式检查文件,可对文件对象使用for循环:

    1
    2
    3
    4
    5
    6
    #file_reader.py

    filename = 'Circumference rate.txt'
    with open(filename) as file_object:
    for line in file_object:
    print(line)

    在文件中每行的末尾都有一个看不见的换行符,而print语句也会加上一个换行符,因此每行末尾都有两个换行符:一个来自文件,一个来自print语句,输出结果如下:

    1
    2
    3
    4
    5
    3.1415926535

    8979323846

    2643383279

    要消除这些多余的空白行,可以使用rstrip():

    1
    2
    3
    4
    5
    6
    #file_reader.py

    filename = 'Circumference rate.txt'
    with open(filename) as file_object:
    for line in file_object:
    print(line.rstrip())

    输出结果如下:

    1
    2
    3
    3.1415926535
    8979323846
    2643383279

    - 9.1.4 创建一个包含文件各行内容的列表

    使用关键字with时,open()返回的文件对象只在with代码块内可用,如果要在with代码块外访问文件的内容,可在with代码块内将文件的各行储存在一个列表当中,并在with代码块外使用该列表:

    1
    2
    3
    4
    5
    6
    7
    8
    #file_reader.py

    filename = 'Circumference rate.txt'
    with open(filename) as file_object:
    lines = file_object.readlines()

    for line in lines:
    print(line.rstrip())

    输出结果与文件内容完全一致

    - 9.1.5 使用文件的内容

    创建一个字符串,它包含文件中储存的所有数字,且没有任何空格:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #pi_string.py

    filename = 'Circumference rate.txt'
    with open(filename) as file_object:
    lines = file_object.readlines()

    pi_string = ''
    for line in lines:
    pi_string += line.rstrip()

    print(pi_string)
    print(len(pi_string))

    打印该字符串以及其长度:

    1
    2
    3.1415926535  8979323846  2643383279
    36

    由于原文件每行左边都有空格,我们可以使用strip()而不是rstrip()来删除它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #pi_string.py

    filename = 'Circumference rate.txt'
    with open(filename) as file_object:
    lines = file_object.readlines()

    pi_string = ''
    for line in lines:
    pi_string += line.strip()

    print(pi_string)
    print(len(pi_string))

    输出结果如下:

    1
    2
    3.141592653589793238462643383279
    32

    Python中有三个去除头尾字符、空白符的函数,它们依次为:

    strip:用来去除头尾字符、空白符(包括\n、\r、\t、’ ‘,即:换行、回车、制表符、空格)

    lstrip:用来去除开头字符、空白符(包括\n、\r、\t、’ ‘,即:换行、回车、制表符、空格)

    rstrip:用来去除结尾字符、空白符(包括\n、\r、\t、’ ‘,即:换行、回车、制表符、空格)
    注意:这些函数都只会删除头和尾的字符,中间的不会删除。
    用法分别为:
    string.strip([chars])
    string.lstrip([chars])
    string.rstrip([chars])
    参数chars是可选的,当chars为空,默认删除string头尾的空白符(包括\n、\r、\t、’ ‘)
    当chars不为空时,函数会被chars解成一个个的字符,然后将这些字符去掉
    它返回的是去除头尾字符(或空白符)的string副本,string本身不会发生改变

    - 9.2 写入文件

    将一条简单的消息储存到文件中:

    1
    2
    3
    4
    5
    #write_message.py

    filename = 'programming.txt'
    with open(filename,'w') as file_object:
    file_object.write("I love programming!")

    调用open()时提供了两个实参,第一个实参也是要打开文件的名称,第二个实参(’w’)告诉Python,我们要以写入模式打开这个文件,打开文件时,可指定读取模式(’r’)、写入模式(’w’)、附加模式(’a’)或者让我们能够读取和写入文件的模式(’r+’),如果省略模式实参,则默认以只读模式打开文件

    附表:Python读写文件各种模式区别
    模式可做操作若文件不存在是否覆盖
    r打开一个文件用于只读报错-
    rb以二进制格式打开一个文件用于只读报错-
    r+打开一个文件用于读和写报错
    rb+以二进制格式打开一个文件用于读和写报错
    w打开一个文件用于只写创建
    wb以二进制格式打开一个文件只用于只写创建
    w+打开一个文件用于读和写创建
    wb+以二进制格式打开一个文件用于读和写创建
    a打开一个文件用于追加创建否,追加写
    ab以二进制格式打开一个文件用于追加创建否,追加写
    a+打开一个文件用于读和写创建否,追加写
    ab+以二进制格式打开一个文件用于追加创建否,追加写

    - 9.3 使用 try-except 代码块处理异常

    当我们尝试将一个数字除以0时,会发生ZeroDivisionError异常:

    1
    2
    3
    4
    5
    >>> print(5/0)
    Traceback (most recent call last):
    File "<pyshell#0>", line 1, in <module>
    print(5/0)
    ZeroDivisionError: division by zero

    此时我们可以编写一个try-except代码块来处理该异常:

    1
    2
    3
    4
    try:
    print(5/0)
    except ZeroDivisionError:
    print("You can't divide by zero!")

    当我们运行该程序时,会出现提示:

    1
    You can't divide by zero!

    try-except代码块中加入else,编写一个只执行除法运算的简单计算器:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    print("Give me two numbers,and I'll divide them.")
    print("Enter 'q' to quit.")

    while True:
    first_number = input("\nFirst number:")
    if first_number == 'q':
    break
    second_number = input("\nSecond number:")
    if second_number == 'q':
    break
    try:
    answer = int(first_number)/int(second_number)
    except ZeroDivisionError:
    print("You can't divide by 0!")
    else:
    print(answer)

    运行程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Give me two numbers,and I'll divide them.
    Enter 'q' to quit.

    First number:45

    Second number:0
    You can't divide by 0!

    First number:36

    Second number:8
    4.5

    First number:q

    若不加入try-except代码块,我们在输入0时,程序就会出现异常而崩溃,而try-except代码块很好的解决了这种问题,而且还起到了提示的作用,同样的,try-except代码块也可以处理其他异常,如FileNotFoundError

    - 9.4 储存数据

    - 9.4.1 使用 json.dump() 和 json.load()

    模块json能够将简单的Python数据结构转储到文件中,并在程序再次运行时加载该文件中的数据;编写一个储存一组数字的简短程序,再编写一个将这些数字读取到内存中的程序,第一个程序将使用 json.dump()来储存这组数据,而第二个程序将使用 json.load()。函数 json.dump()接受两个实参:要储存的数据以及可用于储存数据的文件对象:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #number_writer.py

    import json

    numbers = [2,3,5,7,11,13]

    filename = 'numbers.json'
    with open(filename,'w') as f_obj:
    json.dump(numbers,f_obj)

    先导入模块json,再创建一个数字列表, 通常用文件扩展名.json来指出文件储存的数据为JSON格式,然后以写入模式打开该文件,使用函数json.dump()将数字列表储存到文件numbers.json中,打开该文件,数据的储存格式与Python一样:

    1
    [2, 3, 5, 7, 11, 13]

    再编写一个程序,使用json.load()将这个列表读取到内存中:

    1
    2
    3
    4
    5
    6
    7
    8
    #number_reader.py

    import json

    filename = 'numbers.json'
    with open(filename) as f_obj:
    numbers = json.load(f_obj)
    print(numbers)

    输出结果与number_writer.py中创建的数字列表相同:

    1
    [2, 3, 5, 7, 11, 13]

    进阶:在同一个程序中使用 json.dump()json.load():创建文件username.json储存用户名,从该文件中获取用户名,如果这个文件不存在,就在except代码块中提示用户输入用户名,并将其储存在username.json中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #remember_me.py

    import json

    #如果以前储存了用户名,就加载它
    #否则就提示用户输入用户名并储存它
    filename = 'numbers.json'
    try:
    with open(filename) as f_obj:
    username = json.load(f_obj)
    except FileNotFoundError:
    username = input("What's your name?")
    with open(filename,'w') as f_obj:
    json.dump(username,f_obj)
    print("We'll remember you when you come back, " + username + "!")
    else:
    print("Welcome back, " + username + "!")

    以前没有储存用户名,第一次运行程序:

    1
    2
    What's your name?TRHX
    We'll remember you when you come back, TRHX!

    再次运行程序:

    1
    Welcome back, TRHX!

    - 9.4.2 重构

    代码能够正确运行,但可以做进一步的改进——将代码划分为一系列完成具体工作的函数,这样的过程称为重构,重构让代码更清晰、更易于理解、更容易扩展
    重构remember_me.py,将大部分逻辑放到一个或者多个函数中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    #remember_me.py

    import json

    def greet_user():
    #问候用户,并指出其名字
    filename = 'numbers.json'
    try:
    with open(filename) as f_obj:
    username = json.load(f_obj)
    except FileNotFoundError:
    username = input("What's your name?")
    with open(filename,'w') as f_obj:
    json.dump(username,f_obj)
    print("We'll remember you when you come back, " + username + "!")
    else:
    print("Welcome back, " + username + "!")

    greet_user()

    重构greet_user(),让它不执行这么多任务——将获取储存的用户名的代码移到另一个函数中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    #remember_me.py

    import json

    def get_stored_username():
    #如果储存了用户名,就获取它
    filename = 'numbers.json'
    try:
    with open(filename) as f_obj:
    username = json.load(f_obj)
    except FileNotFoundError:
    return None
    else:
    return username

    def greet_user():
    #问候用户,并指出其名字
    username = get_stored_username()
    if username:
    print("Welcome back, " + username + "!")
    else:
    username = input("What's your name?")
    filename = 'username.json'
    with open(filename,'w') as f_obj:
    json.dump(username,f_obj)
    print("We'll remember you when you come back, " + username + "!")

    greet_user()

    将greet_user()中的另一个代码块提取出来:将没有储存用户名时提示用户输入的代码放在一个独立的函数中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #remember_me.py

    import json

    def get_stored_username():
    #如果储存了用户名,就获取它
    filename = 'numbers.json'
    try:
    with open(filename) as f_obj:
    username = json.load(f_obj)
    except FileNotFoundError:
    return None
    else:
    return username

    def get_new_username():
    #提示输入用户名
    username = input("What's your name?")
    filename = 'username.json'
    with open(filename,'w') as f_obj:
    json.dump(username,f_obj)
    return username


    def greet_user():
    #问候用户,并指出其名字
    username = get_stored_username()
    if username:
    print("Welcome back, " + username + "!")
    else:
    username = get_new_username()
    print("We'll remember you when you come back, " + username + "!")

    greet_user()

    最终版本实现了每个函数只负责单一而清晰的任务,我们在编写程序时也要像这样,要写出清晰而易于维护和扩展的代码

    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 文件 - - 异常 - - - -
    - - - - - Python3 基础学习笔记 C08 - - /2018/11/11/A14-Python3-basic-C08/ - -
    Python3 基础学习笔记第八章 —— 【类】

    - 8.1 创建类和使用类

    创建一个表示小狗的简单类Dog,根据Dog类创建的每个实例都将储存名字和年龄,赋予每条小狗蹲下(sit())和打滚(roll_over())的能力:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Dog():

    def __init__(self,name,age):
    #初始化属性name和age
    self.name = name
    self.age = age

    def sit(self):
    #模拟小狗被命令时蹲下
    print(self.name.title() + " is now sitting.")

    def roll_over(self):
    #模拟小狗被命令时打滚
    print(self.name.title() + " rolled over!")

    方法init():类中的函数称为方法,本例中方法init()是一个特殊的方法,每当我们根据Dog类创建新实例时,Python都会自动运行它,在方法的名称中,开头和结尾各有两个下划线,这是一种约定,避免Python默认方法与普通方法发生名称冲突,例子中将方法init()定义成了包含三个形参:self、name和age,在这个方法的定义中,形参self必不可少,还必须位于其他形参的前面,Python调用方法init()来创建Dog实例时,将自动传入实参self,每个与类相关联的方法调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法,我们创建Dog实例时,Python将调用Dog类的方法init(),我们将通过实参向Dog()传递名字和年龄;self会自动传递,因此我们不需要传递它,每当我们根据Dog类创建实例时,都只需要给最后两个形参(name和age)提供值;定义的两个变量都有前缀self,以self为前缀的变量都可以供类中的所有方法使用,还可以通过类的任何实例来访问这些变量。self.name = name 获取储存在形参name中的值,并将其储存到变量name中,然后该变量被关联到当前创建的实例。self.age = age 的作用与此类似,像这样可通过实例访问的变量称为属性;Dog还定义了另外两种方法:sit() 和 roll_over() ,由于这些方法不需要额外的信息,如名字和年龄,因此它们只有一个形参self

    在Python 2.7中创建类时,需要在括号内包含单词object:

    1
    2
    class ClassName(object):
    ---snip---

    - 8.2 根据类创建实例

    访问属性:创建一个表示特定小狗的实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Dog():

    def __init__(self,name,age):
    #初始化属性name和age
    self.name = name
    self.age = age

    def sit(self):
    #模拟小狗被命令时蹲下
    print(self.name.title() + " is now sitting.")

    def roll_over(self):
    #模拟小狗被命令时打滚
    print(self.name.title() + " rolled over!")

    my_dog = Dog('willie',6)
    print("My dog's name is " + my_dog.name.title() + ".")
    print("My dog is " + str(my_dog.age) + " years old.")

    让Python创建一条名字为’willie’,年龄为6的小狗,Python使用实参’willie’和6调用Dog类中的方法init()。方法init()创建一个表示特定小狗的示例,并使用我们提供的值来设置属性name和age;在访问实例的属性时,可使用句点表示法,比如该例子中的 my_dog.name;最终程序输出结果如下:

    1
    2
    My dog's name is Willie.
    My dog is 6 years old.

    调用方法:根据Dog类创建实例后,就可以使用句点表示法来调用Dog类中定义的任何方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class Dog():

    def __init__(self,name,age):
    #初始化属性name和age
    self.name = name
    self.age = age

    def sit(self):
    #模拟小狗被命令时蹲下
    print(self.name.title() + " is now sitting.")

    def roll_over(self):
    #模拟小狗被命令时打滚
    print(self.name.title() + " rolled over!")

    my_dog = Dog('willie',6)
    my_dog.sit()
    my_dog.roll_over()

    输出结果如下:

    1
    2
    Willie is now sitting.
    Willie rolled over!

    创建多个实例:可按需求根据类创建任意数量的实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Dog():

    def __init__(self,name,age):
    #初始化属性name和age
    self.name = name
    self.age = age

    def sit(self):
    #模拟小狗被命令时蹲下
    print(self.name.title() + " is now sitting.")

    def roll_over(self):
    #模拟小狗被命令时打滚
    print(self.name.title() + " rolled over!")

    my_dog = Dog('willie',6)
    your_dog = Dog('lucy',8)

    print("My dog's name is " + my_dog.name.title() + ".")
    print("My dog is " + str(my_dog.age) + " years old.")
    my_dog.sit()

    print("\nYour dog's name is " + your_dog.name.title() + ".")
    print("Your dog is " + str(your_dog.age) + " years old.")
    your_dog.roll_over()

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    My dog's name is Willie.
    My dog is 6 years old.
    Willie is now sitting.

    Your dog's name is Lucy.
    Your dog is 8 years old.
    Lucy rolled over!

    - 8.3 使用类和实例

    创建一个表示汽车的类,其中储存了有关汽车的信息,还有一个汇总这些信息的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())

    输出结果如下:

    1
    2018 Audi A9

    - 8.3.1 给属性指定默认值

    类中的每个属性都必须有初始值,如果我们设置了默认值,就无需包含为它提供初始值的形参,下面为8.3的例子添加一个 odometer_reading 的属性,其初值是0,添加一个 odometer_reading() 方法,用于读取汽车的里程表:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odomter(self):
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())
    my_new_car.read_odomter()

    输出结果如下:

    1
    2
    2018 Audi A9
    This car has 0 miles on it.

    - 8.3.2 修改属性的值

    可以以三种不同的方式修改属性的值:直接通过实例进行修改;通过方法进行设置;通过方法进行递增(增加特定的值)

    直接修改属性的值:
    要修改属性的值,最简单的方法就是通过实例直接访问它,将8.3.1中的例子第7行代码 self.odometer_reading = 0 改为 self.odometer_reading = 66,输出结果如下:

    1
    2
     2018 Audi A9
    This car has 66 miles on it.

    通过方法修改属性的值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odomter(self):
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self,mileage):
    self.odometer_reading = mileage

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())

    my_new_car.update_odometer(66)
    my_new_car.read_odomter()

    对Car类所做的唯一修改就是在第17、18行添加了方法 update_odometer(),这个方法接受一个里程值,并将其储存到 self.odometer_reading 中,在倒数第二行,调用了 update_odometer(),并向它提供了一个实参(该实参对应于方法定义中的形参mileage),它将里程数设置为66,而方法 read_odomter() 打印该读数:

    1
    2
    2018 Audi A9
    This car has 66 miles on it.

    可对方法 update_odometer() 进行扩展,使其能够在修改里程表读数时做一些额外的工作,添加一些逻辑,禁止任何人将里程表读数往回调:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 50

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odomter(self):
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self,mileage):
    if mileage >= self.odometer_reading:
    self.odometer_reading = mileage
    else:
    print("You can't roll back an odometer!")

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())

    my_new_car.update_odometer(33)
    my_new_car.read_odomter()

    修改 self.odometer_reading 的默认值为50,当我们再次尝试修改其值为33时,由于小于原来的里程,因此无法修改:

    1
    2
    3
    2018 Audi A9
    You can't roll back an odometer!
    This car has 50 miles on it.

    通过方法对属性的值进行递增:
    有时候需要将属性值递增到特定的量,而不是将其设置为全新的值,假设我们购买了一辆二手车,从购买到登记期间增加了100英里的里程,下面的方法让我们能够传递这个增量,并相应地增加里程表读数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odomter(self):
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self,mileage):
    if mileage >= self.odometer_reading:
    self.odometer_reading = mileage
    else:
    print("You can't roll back an odometer!")

    def increment_odometer(self,miles):
    #将里程表读数增加指定的量
    self.odometer_reading += miles

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())

    my_new_car.update_odometer(6600)
    my_new_car.read_odomter()

    my_new_car.increment_odometer(100)
    my_new_car.read_odomter()

    输出结果如下:

    1
    2
    3
    2018 Audi A9
    This car has 6600 miles on it.
    This car has 6700 miles on it.

    - 8.4 继承

    编写类时,并非总是要从空白开始,如果要编写的类是另一个现成类的特殊版本,可使用继承,一个类继承另一个类时,它自动获得另一个类的所有属性和方法;原有的类称为父类,而新类称为子类,子类继承了其父类的所有属性和方法,同时还可以定义自己的属性和方法;继承的通用语法大致如下:

    1
    2
    3
    4
    5
    6
    7
    8
    class ClassName1(object):
    def __init__(self,name1,name2,name3):
    --snip--

    class ClassName2(ClassName1):
    def __init__(self,name1,name2,name3):
    super().__init__(name1,name2,name3)
    --snip--

    - 8.4.1 子类的方法init()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    class ElectricCar(Car):
    #电动车的独特之处
    def __init__(self,make,model,year):
    #初始化父类的属性
    super().__init__(make,model,year)

    my_new_car = ElectricCar('tesla','model s','2016')
    print(my_new_car.get_descriptive_name())

    创建子类时,父类必须包含在当前文件中,且位于子类前面,定义了子类 ElectricCar,定义子类时,必须在括号内指定父类名称,方法 __init__()接受创建Car实例所需信息,super() 是一个特殊的函数,帮助Python将父类和子类关联起来,让Python调用 ElectricCar 的父类的方法 __init__(),让 ElectricCar 实例包含父类的所有属性,父类也称为超类(superclass),程序输出结果如下:

    1
    2016 Tesla Model S

    - 8.4.2 Python 2.7 中的继承

    在Python 2.7中,ElectricCar类的定义类似于下面这样:

    1
    2
    3
    4
    5
    6
    7
    8
    class Car(object):
    def __init__(self,make,model,year):
    --snip--

    class ElectricCar(Car):
    def __init__(self,make,model,year):
    super(ElectricCar,self).__init__(make,model,year)
    --snip--

    - 8.4.3 给子类定义属性和方法

    让一个类继承另一个类后,可添加区分子类和父类所需的新属性和方法,下面添加一个电动车特有的属性(battery),以及一个描述该属性的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    class ElectricCar(Car):
    def __init__(self,make,model,year):
    super().__init__(make,model,year)
    self.battery_size = 80

    def describe_battery(self):
    print("This car has a " + str(self.battery_size) + "-KWh battery.")

    my_new_car = ElectricCar('tesla','model s','2016')
    print(my_new_car.get_descriptive_name())
    my_new_car.describe_battery()

    输出结果如下:

    1
    2
    2016 Tesla Model S
    This car has a 80-KWh battery.

    - 8.4.4 重写父类的方法

    要重写父类的方法,只需要在子类中定义一个与要重写的父类方法同名的方法即可,这样,Python将不会考虑这个父类的方法,而只关心在子类中定义的相应方法,假设Car类有一个名为 fill_gas_tank() 的方法,对于电动车来说毫无意义,因此可以重写它:

    1
    2
    3
    4
    5
    class ElectricCar(Car):
    --snip--

    def fill_gas_tank(self):
    print("This car doesn't need a gas tank!")

    - 8.4.5 将实例用作属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    class Battery():
    #一次模拟电动车电瓶的简单尝试
    def __init__(self,battery_size=70):
    #初始化电瓶的属性
    self.battery_size = battery_size

    def describe_battery(self):
    #打印一条描述电瓶容量的消息
    print("This car has a " + str(self.battery_size) + "-KWh battery.")

    class ElectricCar(Car):
    def __init__(self,make,model,year):
    super().__init__(make,model,year)
    self.battery = Battery()

    my_new_car = ElectricCar('tesla','model s','2016')
    print(my_new_car.get_descriptive_name())
    my_new_car.battery.describe_battery()

    输出结果如下:

    1
    2
    2016 Tesla Model S
    This car has a 70-KWh battery.

    看起来似乎做了多余的工作,但现在我们可以对电瓶添加更多的描述,而且不会导致 ElectricCar 类混乱不堪,下面再给Battery添加一个方法,使其能够根据电瓶容量报告汽车的续航里程:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    class Car():

    def __init__(self,make,model,year):
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    class Battery():
    #一次模拟电动车电瓶的简单尝试
    def __init__(self,battery_size=70):
    #初始化电瓶的属性
    self.battery_size = battery_size

    def describe_battery(self):
    #打印一条描述电瓶容量的消息
    print("This car has a " + str(self.battery_size) + "-KWh battery.")

    def get_range(self):
    #打印一条消息,指出电瓶的续航里程
    if self.battery_size == 70:
    range = 240
    elif self.battery_size == 90:
    range = 280
    message = "This car can go approximately " + str(range)
    message += " miles on a full charge."
    print(message)

    class ElectricCar(Car):
    def __init__(self,make,model,year):
    super().__init__(make,model,year)
    self.battery = Battery()

    my_new_car = ElectricCar('tesla','model s','2016')
    print(my_new_car.get_descriptive_name())
    my_new_car.battery.describe_battery()
    my_new_car.battery.get_range()

    输出结果如下:

    1
    2
    3
    2016 Tesla Model S
    This car has a 70-KWh battery.
    This car can go approximately 240 miles on a full charge.

    - 8.5 导入类

    Python允许将类储存在模块中,然后在主程序中导入所需的模块

    - 8.5.1 导入单个类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #car.py
    #一个用于表示汽车的类

    class Car():

    def __init__(self,make,model,year):
    #初始化描述汽车的属性
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    #返回整洁的描述性名称
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odomter(self):
    #打印一条消息,指出汽车的里程
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self):
    #将里程表读数设置为指定的值,拒绝将里程表往回拨
    if mileage >= self.odometer_reading:
    self.odometer_reading = mileage
    else:
    print("You can't roll back an odometer!")

    def increment_odometer(self,miles):
    #将里程表读数增加指定的量
    self.odometer_reading += miles

    创建另一个文件——my_car.py,在其中导入Car类并创建其实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #my_car.py

    from car import Car

    my_new_car = Car('audi','a9','2018')
    print(my_new_car.get_descriptive_name())

    my_new_car.odometer_reading = 23
    my_new_car.read_odometer()

    import语句让Python打开模块car,并导入其中的Car类,输出结果如下:

    1
    2
    2018 Audi A9
    This car has 23 miles on it.

    - 8.5.2 在一个模块中储存多个类

    将类Battery和ElectricCar都加入到模块car.py中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    #car.py
    #一组用于表示燃油汽车和电动汽车的类

    class Car():

    def __init__(self,make,model,year):
    #初始化描述汽车的属性
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    #返回整洁的描述性名称
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odometer(self):
    #打印一条消息,指出汽车的里程
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self):
    #将里程表读数设置为指定的值,拒绝将里程表往回拨
    if mileage >= self.odometer_reading:
    self.odometer_reading = mileage
    else:
    print("You can't roll back an odometer!")

    def increment_odometer(self,miles):
    #将里程表读数增加指定的量
    self.odometer_reading += miles

    class Battery():
    #一次模拟电动车电瓶的简单尝试
    def __init__(self,battery_size=70):
    #初始化电瓶的属性
    self.battery_size = battery_size

    def describe_battery(self):
    #打印一条描述电瓶容量的消息
    print("This car has a " + str(self.battery_size) + "-KWh battery.")

    def get_range(self):
    #打印一条消息,指出电瓶的续航里程
    if self.battery_size == 70:
    range = 240
    elif self.battery_size == 90:
    range = 280
    message = "This car can go approximately " + str(range)
    message += " miles on a full charge."
    print(message)

    class ElectricCar(Car):
    #模拟电动车的独特之处
    def __init__(self,make,model,year):
    #初始化父类的属性,再初始化电动车特有的属性
    super().__init__(make,model,year)
    self.battery = Battery()

    新建一个my_electric_car.py的文件,导入ElectricCar类,并创建一辆电动车:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #my_electric_car.py

    from car import ElectricCar

    my_tesla = ElectricCar('tesla','model s','2016')

    print(my_tesla.get_descriptive_name())
    my_tesla.battery.describe_battery()
    my_tesla.battery.get_range()

    输出结果如下:

    1
    2
    3
    2016 Tesla Model S
    This car has a 70-KWh battery.
    This car can go approximately 240 miles on a full charge.

    - 8.5.3 从一个模块中导入多个类

    可根据需要在程序文件中导入任意数量的类,假如我们要在同一个程序中创建普通汽车和电动汽车,就需要将类Car和ElectricCar类都导入,多个类之间用逗号进行分隔:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     #my_car.py

    from car import Car,ElectricCar

    my_audi = Car('audi','a9','2018')
    print(my_audi.get_descriptive_name())

    my_tesla = ElectricCar('tesla','model s','2016')
    print(my_tesla.get_descriptive_name())

    输出结果如下:

    1
    2
    2018 Audi A9
    2016 Tesla Model S

    - 8.5.4 导入整个模块

    导入整个模块后,需要使用句点表示法访问需要的类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #my_car.py

    import car

    my_audi = car.Car('audi','a9','2018')
    print(my_audi.get_descriptive_name())

    my_tesla = car.ElectricCar('tesla','model s','2016')
    print(my_tesla.get_descriptive_name())

    我们导入了整个car模块,需要使用语法 module_name.class_name 访问需要的类,程序输出结果与8.5.3一致:

    1
    2
    2018 Audi A9
    2016 Tesla Model S

    - 8.5.5 导入模块中的所有类

    要导入模块中的所有类,可使用以下语法:

    1
    from module_name import *

    这种导入方法是不推荐的,没有明确指出你使用了模块中的哪些类,还可能引发名称方面的困惑,需要从一个模块中导入很多类时,最好导入整个模块,并使用 module_name.class_name 语法来访问类

    - 8.5.6 在一个模块中导入另一个模块

    有时候需要将类分散到多个模块当中,以免模块太大,或者在同一个模块中储存不相关的类,将类储存在多个模块中时,一个模块中的类可能会依赖于另一个模块中的类,这种情况下,我们可以在前一个模块中导入必要的类,以下例子中,将Car类储存在一个模块当中,并将ElectricCar和Battery类储存在另一个模块当中,将第二个模块命名为electric_car.py,并将ElectricCar和Battery类复制到这个模块中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #electric_car.py
    #一组可用于表示电动汽车的类

    from car import Car

    class Battery():
    #一次模拟电动车电瓶的简单尝试
    def __init__(self,battery_size=70):
    #初始化电瓶的属性
    self.battery_size = battery_size

    def describe_battery(self):
    #打印一条描述电瓶容量的消息
    print("This car has a " + str(self.battery_size) + "-KWh battery.")

    def get_range(self):
    #打印一条消息,指出电瓶的续航里程
    if self.battery_size == 70:
    range = 240
    elif self.battery_size == 90:
    range = 280
    message = "This car can go approximately " + str(range)
    message += " miles on a full charge."
    print(message)

    class ElectricCar(Car):
    #模拟电动车的独特之处
    def __init__(self,make,model,year):
    #初始化父类的属性,再初始化电动车特有的属性
    super().__init__(make,model,year)
    self.battery = Battery()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    #car.py
    #一个可用于表示汽车的类

    class Car():

    def __init__(self,make,model,year):
    #初始化描述汽车的属性
    self.make = make
    self.model = model
    self.year = year
    self.odometer_reading = 0

    def get_descriptive_name(self):
    #返回整洁的描述性名称
    long_name = str(self.year) + ' ' + self.make + ' ' +self.model
    return long_name.title()

    def read_odometer(self):
    #打印一条消息,指出汽车的里程
    print("This car has " + str(self.odometer_reading) + " miles on it.")

    def update_odometer(self):
    #将里程表读数设置为指定的值,拒绝将里程表往回拨
    if mileage >= self.odometer_reading:
    self.odometer_reading = mileage
    else:
    print("You can't roll back an odometer!")

    def increment_odometer(self,miles):
    #将里程表读数增加指定的量
    self.odometer_reading += miles

    现在可以分别从每个模块中导入类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #my_car.py

    from car import Car
    from electric_car import ElectricCar

    my_audi = Car('audi','a9','2018')
    print(my_audi.get_descriptive_name())

    my_tesla = ElectricCar('tesla','model s','2016')
    print(my_tesla.get_descriptive_name())

    输出结果如下:

    1
    2
    2018 Audi A9
    2016 Tesla Model S

    - 8.6 Python标准库

    Python标准库是一组模块,安装的Python都包含它,我们可以使用标准库中的任何函数和类,只需要在程序的开头包含一条简单的import语句,下面以模块collections中的一个类——OrderedDict(创建字典并记录其中的键-值对的添加顺序)为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #favorite_languages.py

    from collections import OrderedDict

    favorite_languages = OrderedDict()

    favorite_languages ['jen'] = 'python'
    favorite_languages ['sarah'] = 'c'
    favorite_languages ['edward'] = 'java'
    favorite_languages ['anly'] = 'python'

    for name,language in favorite_languages.items():
    print(name.title() + "'s favorite languages is " +
    language.title() + ".")

    输出结果如下:

    1
    2
    3
    4
    Jen's favorite languages is Python.
    Sarah's favorite languages is C.
    Edward's favorite languages is Java.
    Anly's favorite languages is Python.

    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - - - 继承 - - - -
    - - - - - Python3 基础学习笔记 C07 - - /2018/11/03/A13-Python3-basic-C07/ - -
    Python3 基础学习笔记第七章 —— 【函数】

    - 7.1 定义函数

    一个简单的函数,命名为 example(),其中,关键字 def 来告诉Python我们要定义一个函数,这就是函数定义

    1
    2
    3
    def example():
    print("Hello world!")
    example()

    输出结果如下:

    1
    Hello world!

    - 7.1.1 向函数传递信息

    在函数定义 def example() 的括号中添加 username,可以让函数接受我们给 username 指定的任何值,在调用函数时给 username 指定一个值,调用 example() 时,可将一个名字传递给它:

    1
    2
    3
    def example(username):
    print("Hello , " + username + '!')
    example('TRHX')

    输出结果如下:

    1
    Hello , TRHX!

    - 7.1.2 实参和形参

    在 7.1.1 的例子中,函数 example() 的定义中,变量 username 是一个形参——函数完成其工作所需的一项信息,在代码 example(‘TRHX’) 中,值’TRHX’是一个实参实参是调用函数时传递给函数的信息,调用函数时,将要让函数使用的信息放在括号内。在 example(‘TRHX’) 中,将实参 ‘TRHX’ 传递给了函数 example,这个值被储存在形参 username 中

    - 7.2 传递实参

    鉴于函数定义中可能包含多个形参,因此函数调用中也可能包含多个实参。向函数传递实参的方式很多,可使用位置实参,这要求实参的顺序与形参的顺序相同;也可以使用关键字实参,其中每个实参都由变量和值组成;还可以使用列表和字典

    - 7.2.1 位置实参

    调用函数时,Python必须将函数调用中的每个实参都关联到函数定义中的一个形参。为此,最简单的方法是基于实参的顺序,这种关联方式被称为位置实参

    1
    2
    3
    4
    def describe_pet(animal_type , pet_name):
    print("I have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")
    describe_pet('hamster' , 'harry')

    输出结果如下:

    1
    2
    I have a hamster.
    My hamster's name is Harry.

    调用函数多次:我们可以根据需要调用函数任意次,要再描述一个宠物,只需要再次调用

    1
    2
    3
    4
    5
    6
    describe_pet() 即可
    def describe_pet(animal_type , pet_name):
    print("I have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")
    describe_pet('hamster' , 'harry')
    describe_pet('dog' , 'willi')

    输出结果如下:

    1
    2
    3
    4
    I have a hamster.
    Myhamster's name is Harry.
    I have a dog.
    My dog's name is Willi.

    - 7.2.2 关键字实参

    关键字实参是传递给函数的名称-值对。直接在实参中将名称和值关联起来,不用考虑函数调用中的实参顺序

    1
    2
    3
    4
    5
    def describe_pet(animal_type , pet_name):
    print("I have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")
    describe_pet(animal_type = 'hamster' , pet_name = 'harry')
    describe_pet(pet_name = 'willi' , animal_type = 'dog' )

    输出结果如下:

    1
    2
    3
    4
    I have a hamster.
    Myhamster's name is Harry.
    I have a dog.
    My dog's name is Willi.

    - 7.2.3 默认值

    编写函数时,可给每个形参指定默认值,在调用函数中给形参提供了实参时,Python将使用指定的实参值,否则将使用形参的默认值

    1
    2
    3
    4
    def describe_pet(pet_name , animal_type = 'dog'):
    print("I have a " + animal_type + ".")
    print("My " + animal_type + "'s name is " + pet_name.title() + ".")
    describe_pet(pet_name = 'willi')

    输出结果如下:

    1
    2
    I have a dog.
    My dog's name is Willi.

    在这个函数定义中,修改了形参的排列顺序,由于给 animal_type 指定了默认值,无需通过实参来指定动物类型,因此在函数调用中只包含一个实参——宠物的名字,然而Python依然将这个实参视为位置实参,因此如果函数调用中只包含宠物的名字,这个实参将关联到函数定义中的第一个形参,这就是需要将 pet_name 放在形参列表开头的原因所在

    注意:使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的形参,这让Python依然能够准确地解读位置实参

    - 7.3 返回值

    函数并非总是直接显示输出,相反,它可以处理一些数据,并返回一个或一组值,函数返回的值被称为返回值,在函数中,可使用 return 语句将值返回到函数调用的代码行

    - 7.3.1 返回简单值

    1
    2
    3
    4
    5
    def name(first_name , last_name):
    full_name = first_name + ' ' + last_name
    return full_name.title()
    student = name('jimi' , 'hendrix')
    print(student)

    输出结果如下:

    1
    Jimi Hendrix

    - 7.3.2 让实参变成可选的

    对 7.3.1 的例子进行改进,扩展函数 name,使其还能够处理中间名:

    1
    2
    3
    4
    5
    def name(first_name , middle_name , last_name):
    full_name = first_name + ' ' + middle_name + ' ' + last_name
    return full_name.title()
    student = name('jimi' , 'lee' , 'hendrix')
    print(student)

    输出结果如下:

    1
    Jimi Lee Hendrix

    然而,如果一个人没有中间名,那么在调用这个函数时就会出错,为了让中间名变成可选的,可以给实参 middle_name 指定一个默认值——空字符串,并在用户没有提供中间名时不使用这个实参,注意需要将 middle_name 移到形参列表的末尾:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def name(first_name , last_name , middle_name = ' '):
    if middle_name:
    full_name = first_name + ' ' + middle_name + ' ' + last_name
    else:
    full_name = first_name + ' ' + last_name
    return full_name.title()
    student = name('jimi' , 'hendrix')
    print(student)
    student = name('jimi' , 'hendrix' , 'lee' )
    print(student)

    输出结果如下:

    1
    2
    Jimi   Hendrix
    Jimi Lee Hendrix

    - 7.3.3 返回字典

    函数可返回任何类型的值,包括列表和字典等较复杂的数据结构:

    1
    2
    3
    4
    5
    def name(first_name , last_name):
    full_name = {'first' : first_name , 'last' : last_name}
    return full_name
    student = name('jimi' , 'hendrix')
    print(student)

    输出结果如下:

    1
    {'first': 'jimi', 'last': 'hendrix'}

    - 7.3.4 结合使用函数和 while 循环

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    def name(first_name , last_name):
    full_name = first_name + ' ' + last_name
    return full_name
    while True:
    print("\nPlease input your name:")
    print("(Enter 'exit' to quit)")
    f_name = input("First_name:")
    if f_name == 'exit':
    break
    l_name = input("Last_name:")
    if l_name == 'exit':
    break
    student = name(f_name , l_name)
    print(student)
    print("Hello, " + student.title() + "!")

    运行程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    Please input your name:
    (Enter 'exit' to quit)
    First_name:jimi
    Last_name:hendrix
    jimi hendrix
    Hello, Jimi Hendrix!

    Please input your name:
    (Enter 'exit' to quit)
    First_name:exit

    - 7.4 传递列表

    1
    2
    3
    4
    5
    6
    def users(names):
    for name in names:
    message = "Hello, " + name.title() + "!"
    print(message)
    usernames = ['hannah' , 'tony' , 'margot']
    users(usernames)

    输出结果如下:

    1
    2
    3
    Hello, Hannah!
    Hello, Tony!
    Hello, Margot!

    - 7.4.1 在函数中修改列表

    将列表传递给函数后,函数就可以对其进行修改,在函数中对这个列表所做的任何修改都是永久性的

    #首先创造一个列表,其中包含一些要打印的设计

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']
    completed_models = []

    #模拟打印每个设计,直到没有未打印的设计为止
    #打印每个设计后,都将其移到列表completed_models中
    while unprinted_designs:
    current_design = unprinted_designs.pop()

    #模拟根据设计制作3D打印模型的过程
    print("Printing model: " + current_design)
    completed_models.append(current_design)

    #显示打印好的所有模型
    print("\nThe following models have been printed: ")
    for completed_model in completed_models:
    print(completed_model)

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    Printing model: dodecahedron
    Printing model: robot pendannt
    Printing model: iphone case

    The following models have been printed:
    dodecahedron
    robot pendannt
    iphone case

    编写两个函数重新组织这些代码,每一个函数都做一件具体的工作,输出结果与原程序相同:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    def print_models(unprinted_designs , completed_models):
    #模拟打印每个设计,直到没有未打印的设计为止
    #打印每个设计后,都将其移到列表completed_models中
    while unprinted_designs:
    current_design = unprinted_designs.pop()

    #模拟根据设计制作3D打印模型的过程
    print("Printing model: " + current_design)
    completed_models.append(current_design)

    def show_completed_models(completed_models):
    #显示打印好的所有模型
    print("\nThe following models have been printed: ")
    for completed_model in completed_models:
    print(completed_model)

    unprinted_designs = ['iphone case' , 'robot pendannt' , 'dodecahedron']
    completed_models = []

    print_models(unprinted_designs , completed_models)
    show_completed_models(completed_models)

    - 7.4.2 禁止函数修改列表

    有时候需要禁止函数修改列表,拿 7.4.1 的例子来说,我们打印了所有设计后,也要保留原来的未打印的设计列表,以供备案,但由于我们将所有的设计都移出了 unprinted_designs,这个列表变成了空的,原来的列表没有了,为了解决这个问题,可向函数传递列表的副本而不是原件;这样函数所做的任何修改都只影响副本,而丝毫不影响原件,要将列表的副本传递给函数,可以像下面这样做:

    1
    function_name(list_name[:])

    切片表示法 [:] 创建列表的副本,在 7.4.1 的例子中如果不想清空未打印的设计列表,可像下面这样调用 print_models():

    1
    print_models(unprinted_designs[:] , completed_models)

    - 7.5 传递任意数量的实参

    Python允许函数从调用语句中收集任意数量的实参

    1
    2
    3
    4
    def make_pizza(*toppings):
    print(toppings)
    make_pizza('pepperoni')
    make_pizza('mushrooms' , 'green peppers' , 'extra cheese')

    形参名 *toppings 中的星号让Python创建一个名为 toppings 的空元组,并将收到的所有值都封装到这个元组中,函数体内的print语句通过生成输出来证明Python能够处理使用一个值调用函数的情形,也能处理使用三个值来调用函数的情形,输出结果如下:

    1
    2
    ('pepperoni',)
    ('mushrooms', 'green peppers', 'extra cheese')

    使用循环语句:

    1
    2
    3
    4
    5
    6
    def make_pizza(*toppings):
    print("\nMaking a pizza with the followiing toppings: ")
    for topping in toppings:
    print("- " + topping)
    make_pizza('pepperoni')
    make_pizza('mushrooms' , 'green peppers' , 'extra cheese')

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8

    Making a pizza with the followiing toppings:
    - pepperoni

    Making a pizza with the followiing toppings:
    - mushrooms
    - green peppers
    - extra cheese

    - 7.5.1 结合使用位置实参和任意数量实参

    如果要让函数接受不同类型的实参,必须在函数定义中将接纳任意数量实参的形参放在最后。Python先匹配位置实参和关键字实参,再将余下的实参都收集到最后一个形参中:

    1
    2
    3
    4
    5
    6
    def make_pizza(size , *toppings):
    print("\nMaking a " + str(size) + "-inch pizza with the followiing toppings: ")
    for topping in toppings:
    print("- " + topping)
    make_pizza(16 , 'pepperoni')
    make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8

    Making a 16-inch pizza with the followiing toppings:
    - pepperoni

    Making a 18-inch pizza with the followiing toppings:
    - mushrooms
    - green peppers
    - extra cheese

    - 7.5.2 使用任意数量的关键字实参

    有时候,需要接受任何数量的实参,但预先我们不知道传递给函数的会是什么样的信息,在这种情况下,可以将函数编写成能够接受任意数量的键-值对——调用语句提供了多少就接受多少:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def build_profile(first , last , **user_info):
    #创建一个字典,其中包括我们知道的有关用户的一切
    profile = {}
    profile['first_name'] = first
    profile['last_name'] = last
    for key , value in user_info.items():
    profile[key] = value
    return profile
    user_profile = build_profile('albert' , 'einstein' , location = 'princeton' , field = 'physics')
    print(user_profile)

    形参 **user_info 中的两个星号让Python创建一个名为 user_info 的空字典,并将收到的所有名称-值对都封装到这个字典中,在这个函数中,可以像访问其他字典那样访问 user_info 中的名字-值对,程序运行结果如下:

    1
    {'first_name': 'albert', 'last_name': 'einstein', 'location': 'princeton', 'field': 'physics'}

    - 7.6 将函数储存在模块中

    更进一步,我们可以把函数储存在被称为模块的独立文件中,再将模块导入到主程序中,import 语句运行在当前运行的程序文件中使用模块中的代码

    - 7.6.1 导入整个模块

    要让函数是可导入的,得先创建模块,模块是扩展名为.py的文件,包含要导入到程序中的代码,下面将创建一个包含函数 make_pizza() 的模块

    1
    2
    3
    4
    5
    6
    7
    #pizza.py

    def make_pizza(size , *toppings):
    #概述要制作的比萨
    print("\nMaking a " + str(size) + "-inch pizza with the followiing toppings: ")
    for topping in toppings:
    print("- " + topping)

    接下来,我们在 pizza.py 所在的目录中创建另一个名为 making_pizzas.py 的文件,在这个文件中导入刚刚创建的模块,在调用 make_pizza() 两次:

    1
    2
    3
    4
    5
    #making_pizzas.py

    import pizza
    pizza.make_pizza(16 , 'pepperoni')
    pizza.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    Python在读取这个文件时,代码行 import pizza 让Python打开文件 pizza.py,并在幕后将其中所有函数都复制到这个程序中,在 making_pizzas.py 中,可以使用 pizza.py 中定义的所有函数,要调用被导入的模块中的函数,可指定导入的模块的名称 pizza 和函数名 make_pizza(),并使用句点分隔它们,最终运行结果与原程序相同:

    1
    2
    3
    4
    5
    6
    7
    8

    Making a 16-inch pizza with the followiing toppings:
    - pepperoni

    Making a 18-inch pizza with the followiing toppings:
    - mushrooms
    - green peppers
    - extra cheese

    - 7.6.2 导入特定的函数

    导入模块中特定的函数,可以使用以下语法:

    1
    from module_name import function_name

    通过用逗号分隔函数名,可根据需要从模块中导入任意数量的函数:

    1
    from module_name import function_0 , function_1 , function_2

    以前面的 making_pizzas.py 为例,如果只想导入要使用的函数,代码类似于下面这样:

    1
    2
    3
    4
    from pizza import make_pizza

    make_pizza(16 , 'pepperoni')
    make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    - 7.6.3 使用 as 给函数指定别名

    如果要导入的函数名称可能与程序中现有的名称冲突,或者函数的名称太长,可指定简短而独一无二的别名,要给函数指定别名,需要在导入它的时候这样做,通用语法为:

    1
    from module_name import function_name as fn

    同样以前面的 making_pizzas.py 为例:

    1
    2
    3
    4
    from pizza import make_pizza as mp

    mp(16 , 'pepperoni')
    mp(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    - 7.6.4 使用 as 给模块指定别名

    我们还可以给模块指定别名,通用语法为:

    1
    import module_name as mn

    同样以前面的 making_pizzas.py 为例:

    1
    2
    3
    4
    import pizza as p

    p.make_pizza(16 , 'pepperoni')
    p.make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    - 7.6.5 导入模块中的所有函数

    导入模块中所有函数的通用语法为:

    1
    from module_name import *

    同样以前面的 making_pizzas.py 为例:

    1
    2
    3
    4
    from pizza import *

    make_pizza(16 , 'pepperoni')
    make_pizza(18 , 'mushrooms' , 'green peppers' , 'extra cheese')

    import 语句中的星号让Python将模块 pizza 中的每个函数都复制到这个程序中,由于导入了每个函数,可通过名称来调用每个函数,而不需要用句点表示法,然而,如果模块中有函数的名称与项目中的名称相同,就有可能导致意想不到的结果,最佳的做法是,要么只导入我们需要使用的函数,要么导入整个模块并使用句点表示法

    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 函数 - - 模块 - - - -
    - - - - - Python3 基础学习笔记 C06 - - /2018/10/30/A12-Python3-basic-C06/ - -
    Python3 基础学习笔记第六章 —— 【用户输入和 while 循环】

    - 6.1 函数 input() 的工作原理

    函数 input() 让程序暂停运行,等待用户输入一些文本。获取用户输入后,Python将其储存在一个变量当中,以方便你使用;函数 input() 返回为 string 类型

    1
    2
    message = input("Please tell me your name:")
    print("Hello , " + message + "!")

    输出结果如下:

    1
    2
    Please tell me your name:anliy
    Hello , anliy!

    进阶:

    1
    2
    3
    4
    message = "Please tell me your name so that we can personalize the messages you see."
    message += "\nWhat's your first name?"
    name = input(message)
    print("\nHello , " + name + "!")

    输出结果如下:

    1
    2
    3
    4
    Please tell me your name so that we can personalize the messages you see.
    What's your first name?trhx

    Hello , trhx!

    - 6.1.1 使用 int() 来获取数值输入

    使用函数 input() 时,Python会将用户输入解读为字符串:

    1
    2
    3
    4
    >>> age = input("How old are you?")
    How old are you?19
    >>> age
    '19'

    为了解决这个问题,可以使用函数 int() ,它让Python将输入视为数值:

    1
    2
    3
    4
    5
    >>> age = input("How old are you?")
    How old are you?19
    >>> age = int(age)
    >>> age
    19

    实例:

    1
    2
    3
    4
    5
    6
    age = input("Please tell me your age:")
    age = int(age)
    if age >= 18:
    print("You are old enough to go to the Internet bar!")
    else:
    print("You are not old enough to go to Internet bar!")

    输出结果如下:

    1
    2
    Please tell me your age:17
    You are not old enough to go to Internet bar!

    - 6.1.2 求模运算符

    处理数值信息时,求模运算符(%)是一个很有用的工具,它将两个数相除并返回余数:

    1
    2
    3
    4
    5
    6
    7
    8
    >>> 4 % 3
    1
    >>> 5 % 3
    2
    >>> 8 % 2
    0
    >>> 7 % 3
    1

    - 6.1.3 在 Python 2.7 中获取输入

    如果使用 Python 2.7,应该使用函数 raw_input() 来提示用户输入,这个函数与 Python 3 中的 input() 一样,也将输入解读为字符串;Python 2.7 也包含函数 input(),但它将用户输入解读为Python代码,并尝试运行它们

    - 6.2 while 循环

    for 循环用于针对集合中的每一个元素的一个代码块,而 while 循环不断地运行,直到指定的条件不满足为止

    - 6.2.1 使用 while 循环

    一个简单的 while 循环:

    1
    2
    3
    4
    num = 1
    while num < 5:
    print(num)
    num += 1

    输出结果如下:

    1
    2
    3
    4
    1
    2
    3
    4

    - 6.2.2 让用户选择退出循环

    1
    2
    3
    4
    5
    6
    prompt = "\nTell me something, and I will repeat it back to you:"
    prompt += "\nEnter 'quit' to end the program."
    message = " "
    while message != 'quit':
    message = input(prompt)
    print(message)

    运行程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    Tell me something, and I will repeat it back to you:
    Enter 'quit' to end the program.Hello everyone!
    Hello everyone!

    Tell me something, and I will repeat it back to you:
    Enter 'quit' to end the program.Hello again!
    Hello again!

    Tell me something, and I will repeat it back to you:
    Enter 'quit' to end the program.quit
    quit

    - 6.2.3 使用标志

    在要求很多条件都满足才继续运行的程序中,可以定义一个变量,用于判断整个程序是否处于活动状态,这个变量称为标志

    1
    2
    3
    4
    5
    6
    7
    8
    9
    prompt = "\nTell me something, and I will repeat it back to you:"
    prompt += "\nEnter 'quit' to end the program."
    active = True
    while active:
    message = input(prompt)
    if message == 'quit':
    active = False
    else:
    print(message)

    运行结果与6.2.2一致

    - 6.2.4 使用 break 退出循环

    要立即退出 while 循环,不再运行循环中余下的代码,也不管条件测试的结果如何,可使用 break 语句,break 语句用于控制程序流程,可使用它来控制哪些代码将执行,哪些代码不执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    prompt = "\nPlease enter the name of a city you have visited:"
    prompt += "\nEnter 'quit' when you are finished."
    active = True
    while active:
    city = input(prompt)
    if city == 'quit':
    break
    else:
    print("I'd love to go to " + city.title() + "!")

    运行程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11

    Please enter the name of a city you have visited:
    Enter 'quit' when you are finished.Shanghai
    I'd love to go to Shanghai!

    Please enter the name of a city you have visited:
    Enter 'quit' when you are finished.Beijing
    I'd love to go to Beijing!

    Please enter the name of a city you have visited:
    Enter 'quit' when you are finished.quit

    在任何Python循环中都可以使用break语句,例如,可以使用break语句来退出遍历列表或字典

    - 6.2.5 在循环中使用 continue

    要返回到循环开头,并根据条件测试结果决定是否继续执行循环,可使用 continue 语句,它不像 break 语句那样不再执行余下的代码并退出整个循环,例如,从1到10只打印其中奇数:

    1
    2
    3
    4
    5
    6
    number =0
    while number < 10:
    number += 1
    if number % 2 == 0:
    continue
    print(number)

    输出结果如下:

    1
    2
    3
    4
    5
    1
    3
    5
    7
    9

    - 6.3 使用 while 循环来处理列表和字典

    for循环是一种遍历列表的有效方式,但在for循环中不应修改列表,否则将导致Python难以跟踪其中的元素,要在遍历列表的同时对其进行修改,可使用while循环

    - 6.3.1 在列表之间移动元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    unconfirmed_users = ['alice' , 'brian' , 'candace']
    confirmed_users = []
    while unconfirmed_users:
    current_user = unconfirmed_users.pop()
    print("Verifying user: " + current_user.title())
    confirmed_users.append(current_user)
    print("\nThe following users have been confirmed:")
    for confirmed_user in confirmed_users:
    print(confirmed_user.title())

    首先创建一个未验证用户列表,其中包含用户Alice、Brian和Candace,还创建了一个空列表,用于存储已验证的用户,程序中的 while 循环将不断地运行,直到列表 unconfirmed_users 变成空的。在这个循环中,函数pop() 以每次一个的方式从列表 unconfirmed_users 末尾删除未验证的用户。由于Candace位于列表 unconfirmed_users 的末尾,因此其名字将首先被删除、存储到变量 current_user 中并加入到列表 confirmed_users 中。接下来是Brian,然后是Alice

    为模拟用户验证过程,我们打印一条验证消息并将用户加入到已验证用户列表中。未验证用户列表越来越短,而已验证用户列表越来越长。未验证用户列表为空后结束循环,再打印已验证用户列表:

    1
    2
    3
    4
    5
    6
    7
    8
    Verifying user: Candace
    Verifying user: Brian
    Verifying user: Alice

    The following users have been confirmed:
    Candace
    Brian
    Alice

    - 6.3.2 删除包含特定值的所有列表元素

    可以使用方法 remove() 来删除列表中特定的值,但如果要删除的值在列表中出现了多次,方法 remove() 就不管用了,如果要删除列表中所有包含特定值的元素则可以使用 while 循环:

    1
    2
    3
    4
    5
    names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']
    print(names)
    while 'candace' in names:
    names.remove('candace')
    print(names)

    输出结果如下:

    1
    2
    ['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']
    ['alice', 'alice', 'brian', 'alix', 'heliy']

    使用方法 remove() 做对比:

    1
    2
    3
    4
    names = ['alice' , 'candace' , 'alice' , 'brian' , 'alix' , 'candace' , 'heliy']
    print(names)
    names.remove('candace')
    print(names)

    输出结果如下:

    1
    2
    ['alice', 'candace', 'alice', 'brian', 'alix', 'candace', 'heliy']
    ['alice', 'alice', 'brian', 'alix', 'candace', 'heliy']

    - 6.3.3 使用用户输入来填充字典

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    responses = {}

    #设置一个标志,指出调查是否继续
    polling_active = True

    while polling_active:
    #提示输入被调查者的姓名和回答
    name = input("\nWhat's your name?")
    response = input("What kind of fruit do you like?")

    #将答卷储存在字典中
    responses[name] = response

    #询问是否还有其他人要参与回答
    repeat = input("Would you like to let another person respond?(Yes/No)")
    if repeat == 'No':
    polling_active = False

    #调查结束,显示结果
    print("\n------ Poll Results ------")
    for name , response in responses.items():
    print(name + " like " + response + ".")

    运行程序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    What's your name?TRHX
    What kind of fruit do you like?apple
    Would you like to let another person respond?(Yes/No)Yes

    What's your name?TRHXCC
    What kind of fruit do you like?banana
    Would you like to let another person respond?(Yes/No)No

    ------ Poll Results ------
    TRHX like apple.
    TRHXCC like banana.
    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - input()函数 - - while循环 - - - -
    - - - - - Python3 基础学习笔记 C05 - - /2018/10/27/A11-Python3-basic-C05/ - -
    Python3 基础学习笔记第五章 —— 【字典】

    - 5.1 一个简单的字典

    1
    2
    3
    fruits = {'apple' : 'red' , 'number' : 5}
    print(fruits['apple'])
    print(fruits['number'])

    输出结果如下:

    1
    2
    red
    5

    在Python中,字典是一系列键-值对。每个键都与一个值相关联,你可以使用键来访问与之相关联的值。与键相关联的值可以是数字、字符串、列表乃至字典。事实上,可以将任何Python对象用作字典中的值。键-值对是两个相关联的值。在指定键时,Python将返回与之相关联的值。键和值之间用冒号分隔,而键-值对之间用逗号分隔。在字典中,想储存多少个键-值对都可以

    - 5.1.1 访问字典中的值

    要获取与键相关联的值,可依次指定字典名和放在方括号内的键:

    1
    2
    3
    fruits = {'apple' : 'red' , 'number' : 5}
    number_fruits = fruits['number']
    print("The number of apple is " + str(number_fruits) + "!")

    输出结果如下:

    1
    The number of apple is 5!

    - 5.1.2 添加键-值对

    字典是一种动态结构,可随时在其中添加键-值对。要添加键-值对,可依次指定字典名、用方括号括起来的键和相关联的值

    1
    2
    3
    4
    5
    fruits = {'apple' : 'red' , 'number1' : 5}
    print(fruits)
    fruits['banana'] = 'yellow'
    fruits['number2'] = 13
    print(fruits)

    输出结果如下:

    1
    2
    {'apple': 'red', 'number1': 5}
    {'apple': 'red', 'number1': 5, 'banana': 'yellow', 'number2': 13}
    注意:键-值对的排列顺序与添加顺序不同。Python不关心键-值对的添加顺序,而只关心键和值之间的关联关系

    有时候为了方便也可以先使用一对空的花括号定义一个字典,再分行添加各个键-值对:

    1
    2
    3
    4
    fruits = {}
    fruits['banana'] = 'yellow'
    fruits['number2'] = 13
    print(fruits)

    输出结果如下:

    1
    {'banana': 'yellow', 'number2': 13}

    - 5.1.3 修改字典中的值

    要修改字典中的值,可依次指定字典名、用方括号括起来的键以及与该键相关联的新值

    1
    2
    3
    4
    fruits = {'color' : 'red'}
    print("The color of the fruits is " + fruits['color'] + "!")
    fruits['color'] = 'yellow'
    print("The color of the fruits is " + fruits['color'] + " now!")

    输出结果如下:

    1
    2
    The color of the fruits is red!
    The color of the fruits is yellow now!

    进阶:对一个能够以不同速度移动的外星人的位置进行跟踪,为此,我们将储存该外星人的当前速度,并据此确定该外星人将向右移动多远:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    alien = {'x_position': 0, 'y_position': 25, 'speed': 'medium'}
    print("Original x-position: " + str(alien['x_position']))

    #向右移动外星人,据外星人当前速度决定将其移动多远
    if alien['speed'] == 'slow':
    x_increment = 1
    elif alien['speed'] == 'medium':
    x_increment = 2
    else:
    x_increment = 3

    #新位置等于老位置加上增量
    alien['x_position'] = alien['x_position'] + x_increment
    print("New x_position: " + str(alien['x_position']))

    输出结果如下:

    1
    2
    Original x-position: 0
    New x_position: 2

    - 5.1.4 删除键-值对

    对于字典中不再需要的信息,可使用del语句将相应的键-值对彻底删除。使用del语句时,必须指定字典名和要删除的键

    1
    2
    3
    4
    fruits = {'apple' : 'red' , 'number' : 5}
    print(fruits)
    del fruits['number']
    print(fruits)

    输出结果如下:

    1
    2
    {'apple': 'red', 'number': 5}
    {'apple': 'red'}

    - 5.1.5 由类似对象组成的字典

    字典储存的可以是一个对象的多种信息,也可以储存众多对象的同一种信息,例如要调查很多人最喜欢的编程语言:

    1
    2
    3
    4
    5
    6
    7
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    print("Sarah's favorite languages is " + favorite_languages['sarah'].title() + "!")

    输出结果如下:

    1
    Sarah's favorite languages is C!

    - 5.2 遍历字典

    - 5.2.1 方法 items() 遍历所有的键-值对

    使用for循环来遍历字典:

    1
    2
    3
    4
    5
    6
    7
    8
    name = {
    'username' : 'efermi' ,
    'first' : 'enrico' ,
    'last' : 'fermi' ,
    }
    for key , value in name.items():
    print("\nKey: " + key)
    print("Value: " + value)

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9

    Key: username
    Value: efermi

    Key: first
    Value: enrico

    Key: last
    Value: fermi

    for语句的第二部分包含字典和方法items(),它返回一个键-值对列表。接下来,for循环依次将每个键-值对储存到指定的两个变量中

    1
    2
    3
    4
    5
    6
    7
    8
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    for name, language in favorite_languages.items():
    print(name.title() + "'s favorite language is " + language.title() + ".")

    输出结果如下:

    1
    2
    3
    4
    Jen's favorite language is Python.
    Sarah's favorite language is C.
    Edward's favorite language is Ruby.
    Phil's favorite language is Java.

    - 5.2.2 方法 keys() 遍历字典中所有的键

    在不需要使用字典中的值时,方法key()很有用,下面来遍历字典favorite_languages,并将每个被调查者的名字都打印出来:

    1
    2
    3
    4
    5
    6
    7
    8
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    for name in favorite_languages.keys():
    print(name.title())

    输出结果如下:

    1
    2
    3
    4
    Jen
    Sarah
    Edward
    Phil

    遍历字典时,会默认遍历所有的键,因此,如果将上述代码中的for name in favorite_languages.keys():替换为for name in favorite_languages:输出结果将不变
    进阶:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    friends = ['phil', 'sarah']
    for name in favorite_languages.keys():
    print(name.title())
    if name in friends:
    print("Hi " + name + ", I see your favorite languages is " + favorite_languages[name].title() + "!")

    输出结果如下:

    1
    2
    3
    4
    5
    6
    Jen
    Sarah
    Hi sarah, I see your favorite languages is C!
    Edward
    Phil
    Hi phil, I see your favorite languages is Java!

    - 5.2.3 函数 sorted() 按顺序遍历字典中的所有键

    字典总是明确地记录键和值之间的关联关系,但获取字典的元素时,获取顺序是不可预测的,要以特定的顺序返回元素,一种办法是在for循环中对返回的键进行排序,为此,可以使用函数sorted()来获得按特定顺序排列的键列表的副本:

    1
    2
    3
    4
    5
    6
    7
    8
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    for name in sorted(favorite_languages.keys()):
    print(name.title())

    输出结果如下:

    1
    2
    3
    4
    Edward
    Jen
    Phil
    Sarah

    - 5.2.4 方法 values() 遍历字典中的所有值

    1
    2
    3
    4
    5
    6
    7
    8
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'java' ,
    }
    for languages in favorite_languages.values():
    print(languages.title())

    输出结果如下:

    1
    2
    3
    4
    Python
    C
    Ruby
    Java

    这种做法提取字典中所有的值,而没有考虑是否重复,为剔除重复项,可使用集合(set),集合类似于列表,但每个元素都必须是独一无二的:

    1
    2
    3
    4
    5
    6
    7
    8
    favorite_languages = {
    'jen' : 'python' ,
    'sarah' : 'c' ,
    'edward' : 'ruby' ,
    'phil' : 'python' ,
    }
    for languages in set(favorite_languages.values()):
    print(languages.title())

    输出结果如下:

    1
    2
    3
    C
    Python
    Ruby

    - 5.3 嵌套

    有时候,需要将一系列字典储存在列表中,或将列表作为值储存在字典中,这称为嵌套。可以在列表中嵌套字典、在字典中嵌套列表甚至在字典中嵌套字典

    - 5.3.1 字典列表

    下面代码创建三个字典,每个字典都表示一个个学生,将这三个字典都放到一个名为students的列表当中,遍历列表将每个学生都打印出来:

    1
    2
    3
    4
    5
    6
    student_0 = {'name' : 'anily' , 'class' : 2}
    student_1 = {'name' : 'nikey' , 'class' : 5}
    student_2 = {'name' : 'heyk' , 'class' : 3}
    students = [student_0 , student_1 , student_2]
    for student in students:
    print(student)

    输出结果如下:

    1
    2
    3
    {'name': 'anily', 'class': 2}
    {'name': 'nikey', 'class': 5}
    {'name': 'heyk', 'class': 3}

    进阶:使用 range() 自动生成三十个外星人:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #创建一个用于存储外星人的空列表
    aliens = []

    #创建三十个绿色的外星人
    for alien_number in range(30):
    new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'}
    aliens.append(new_alien)

    #显示前五个外星人
    for alien in aliens[:5]:
    print(alien)
    print("......")

    #显示创建了多少外星人
    print("Total number of aliens: " + str(len(aliens)))

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    ......
    Total number of aliens: 30

    在上述例子中,虽然每个外星人都具有相同特征,但在Python看来,每个外星人都是独立的,我们可以独立地修改每个外星人:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    aliens = []
    for alien_number in range(30):
    new_alien = {'color' : 'green' , 'points' : 5 , 'speed' : 'slow'}
    aliens.append(new_alien)
    for alien in aliens[0:3]:
    if alien['color'] == 'green':
    alien['color'] = 'yellow'
    alien['points'] = 10
    alien['speed'] = 'medium'
    for alien in aliens[:5]:
    print(alien)
    print("......")
    print("Total number of aliens: " + str(len(aliens)))

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    {'color': 'yellow', 'points': 10, 'speed': 'medium'}
    {'color': 'yellow', 'points': 10, 'speed': 'medium'}
    {'color': 'yellow', 'points': 10, 'speed': 'medium'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    {'color': 'green', 'points': 5, 'speed': 'slow'}
    ......
    Total number of aliens: 30

    - 5.3.2 在字典中存储列表

    有时候需要将列表储存在字典中,而不是将字典储存在列表中:
    例一:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #储存所点比萨的信息
    pizza = {
    'crust' : 'thick' ,
    'toppings' : ['mushrooms' , 'extra chees'] ,
    }

    #概述所点的比萨
    print("You ordered a " + pizza['crust'] + "-crust pizza" + "with the following toppings :" )
    for topping in pizza['toppings']:
    print("\t" + topping)

    输出结果如下:

    1
    2
    3
    You ordered a thick-crust pizzawith the following toppings :
    mushrooms
    extra chees

    例二:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    favorite_languages = {
    'jen' : ['python' , 'ruby'] ,
    'sarah' : ['c'] ,
    'edward' : ['go' , 'ruby'] ,
    'phil' : ['python' , 'java'] ,
    }
    for name , languages in favorite_languages.items():
    print("\n" + name.title() + "'s favorite languages are:")
    for language in languages:
    print("\t" + language.title())

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    Jen's favorite languages are:
    Python
    Ruby

    Sarah's favorite languages are:
    C

    Edward's favorite languages are:
    Go
    Ruby

    Phil's favorite languages are:
    Python
    Java

    - 5.3.3 在字典中存储字典

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    users = {
    'aeinstein' : {
    'first' : 'albert' ,
    'last' : 'einstein' ,
    'location' : 'princeton' ,
    } ,
    'mcurie' : {
    'first' : 'marie' ,
    'last' : 'curie' ,
    'location' : 'paris' ,
    } ,
    }
    for username , user_info in users.items():
    print("\nUsername : " + username)
    full_name = user_info['first'] + " " + user_info['last']
    location = user_info['location']
    print("\tFull name : " + full_name.title())
    print("\tlocation : " + location .title())

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8

    Username : aeinstein
    Full name : Albert Einstein
    location : Princeton

    Username : mcurie
    Full name : Marie Curie
    location : Paris
    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 字典 - - - -
    - - - - - Python3 基础学习笔记 C04 - - /2018/10/24/A10-Python3-basic-C04/ - -
    Python3 基础学习笔记第四章 —— 【if语句】

    - 4.1 一个简单的数列

    给定一个汽车列表,将其中每一辆汽车的名称打印出来,要求打印 ‘bmw’ 时所有字母都要大写,其余名称只需要首字母大写:

    1
    2
    3
    4
    5
    6
    cars = ['audi' , 'bmw' , 'subaru' , 'toyota']
    for car in cars:
    if car == 'bmw':
    print(car.upper())
    else:
    print(car.title())

    输出结果如下:

    1
    2
    3
    4
    Audi
    BMW
    Subaru
    Toyota

    - 4.1.1 检查特定值是否包含在列表当中

    要判断特定的值是否已包含在列表当中,可使用关键字 in

    1
    2
    3
    4
    user_names = ['andia' , 'david' , 'liwa']
    user = 'andia'
    if user in user_names:
    print(user.title() + "is in user_name.")

    输出结果如下:

    1
    Andiais in user_name.

    要判断特定的值是否不包含在列表当中,可使用关键字 not in

    1
    2
    3
    4
    user_names = ['andia' , 'david' , 'liwa']
    user = 'kivle'
    if user not in user_names:
    print(user.title() + "is not in user_name.")

    输出结果如下:

    1
    Kivleis not in user_name.

    - 4.2 if-else 语句

    1
    2
    3
    4
    5
    6
    7
    age = input("请输入你的年龄查看是否可以去网吧:")
    if int(age) >= 18:
    print("You are old enough to go to the net bar!")
    print("You should go to net bar less,study more!")
    else:
    print("You are too young to go to the net bar!")
    print("Wait until you are 18 to go to the net bar!")

    分别输入19和15,输出结果如下:

    1
    2
    3
    请输入你的年龄查看是否可以去网吧:19
    You are old enough to go to the net bar!
    You should go to net bar less,study more!
    1
    2
    3
    请输入你的年龄查看是否可以去网吧:15
    You are too young to go to the net bar!
    Wait until you are 18 to go to the net bar!

    - 4.3 if-elif-else 结构

    1
    2
    3
    4
    5
    6
    7
    8
    age = 12
    if age < 4:
    price = 0
    elif age < 18:
    price = 5
    else:
    price = 10
    print("Your admission cost is $" + str(price) + ".")

    输出结果如下:

    1
    Your admission cost is $5.

    - 4.3.1 使用多个 elif 代码块

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    age = 20
    if age < 4:
    price = 0
    elif age < 18:
    price = 5
    elif age < 65:
    price = 15
    else:
    price = 10
    print("Your admission cost is $" + str(price) + ".")

    输出结果如下:

    1
    Your admission cost is $15.

    - 4.3.2 省略 else 代码块

    Python并不要求 if-elif 结构后面必须有 else 代码块:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    age = 20
    if age < 4:
    price = 0
    elif age < 18:
    price = 5
    elif age < 65:
    price = 15
    elif age >= 65:
    price = 10
    print("Your admission cost is $" + str(price) + ".")

    输出结果仍与3.3.1一样

    - 4.4 测试多个条件

    if-elif-else结构功能强大,但仅适用于只有一个条件满足的情况:遇到通过了的测试后,Python就会跳过余下的测试:

    1
    2
    3
    4
    5
    6
    7
    8
     names = ['Zhangshan' , 'Wanger']
    if 'Zhangshan' in names:
    print("Zhangshan is here!")
    if 'Wanger' in names:
    print("Wanger is here!")
    if 'Xiaoming' in names:
    print("Xiaoming is here!")
    print("All the students are here!")

    输出结果如下:

    1
    2
    3
    Zhangshan is here!
    Wanger is here!
    All the students are here!

    相同的程序,如果使用 if-elif-else 结构,代码将不能正确运行:

    1
    2
    3
    4
    5
    6
    7
    8
    names = ['Zhangshan' , 'Wanger']
    if 'Zhangshan' in names:
    print("Zhangshan is here!")
    elif 'Wanger' in names:
    print("Wanger is here!")
    elif 'Xiaoming' in names:
    print("Xiaoming is here!")
    print("All the students are here!")

    输出结果如下:

    1
    2
    Zhangshan is here!
    All the students are here!

    总之,如果我们只想执行一个代码块,就使用 if-elif-else 结构;如果要运行多个代码块,就必须使用一系列独立的 if 语句!

    - 4.5 使用 if 语句处理列表

    - 4.5.1 检查特殊元素

    对3.4例子改版,加入姓名 ‘Xiaoming’,当检索到Xiaoming时告诉他,他妈妈叫他回家吃饭

    1
    2
    3
    4
    5
    6
    7
    names = ['Zhangshan' , 'Wanger' , 'Xiaoming']
    for name in names:
    if name == 'Xiaoming':
    print("Xiaoming,Your mother told you to go home for dinner!")
    else:
    print(name +"is here!")
    print("All the students are here!")

    输出结果如下:

    1
    2
    3
    4
    Zhangshanis here!
    Wangeris here!
    Xiaoming,Your mother told you to go home for dinner!
    All the students are here!

    - 4.5.2 确定列表不是空的

    在检索姓名前检查姓名是否为空,不为空则打印出所有姓名,为空则提示没有姓名:

    1
    2
    3
    4
    5
    6
    7
    names = []
    if names:
    for name in names:
    print(name +" is here!")
    print("All the students are here!")
    else:
    print("There is no students!")

    输出结果如下:

    1
    There is no students!
    在if语句中将列表名用在条件表达式中时,Python将在列表至少包含一个元素时返回Ture,并在列表为空时返回False

    - 4.5.3 使用多个列表

    两个列表names_1和names_2,要求输出既在names_2中又在names_1中的元素:

    1
    2
    3
    4
    5
    6
    names_1 = ['Zhangshan' , 'Liyang'  , 'Wanger' , 'Tangyang' , 'Xiaoming']
    names_2 = ['Liyang' , 'Zhangwei' , 'Tangyang']
    for names in names_2:
    if names in names_1:
    print(names +" is here!")
    print("All the students are here!")

    输出结果如下:

    1
    2
    3
    Liyang is here!
    Tangyang is here!
    All the students are here!
    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - if语句 - - - -
    - - - - - Python3 基础学习笔记 C03 - - /2018/10/11/A09-Python3-basic-C03/ - -
    Python3 基础学习笔记第三章 —— 【操作列表】

    - 3.1遍历整个列表

    使用 for 循环来遍历整个列表:

    1
    2
    3
    names = ['alice' , 'david' , 'liwei']
    for name in names:
    print(name)

    输出结果如下:

    1
    2
    3
    alice
    david
    liwei

    for循环让Python从列表names中取出一个名字,并将其储存在变量name中,最后 让Python打印前面储存到变量name中的名字,对于列表中的每个名字,Python都将 重复执行后两行代码,将列表names中的每个名字都打印出来

    - 3.1.1在for循环中执行更多的操作

    在for循环中,可对每个元素执行任何操作,下面对前面的示例进行扩展:

    例一:

    1
    2
    3
    names = ['alice' , 'david' , 'liwei']
    for name in names:
    print(name.title() + ", that was a good man!")

    输出结果如下:

    1
    2
    3
    Alice, that was a good man!
    David, that was a good man!
    Liwei, that was a good man!

    例二:

    1
    2
    3
    4
    5
    names = ['alice' , 'david' , 'liwei']
    for name in names:
    print(name.title() + ", that was a good man!")
    print("I can't wait to see you again," + name.title() + ".\n")
    print("Nice to meet you!")

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Alice, that was a good man!
    I can't wait to see you again,Alice.

    David, that was a good man!
    I can't wait to see you again,David.

    Liwei, that was a good man!
    I can't wait to see you again,Liwei.

    Nice to meet you!

    - 3.2 range()函数

    Python使用range()函数能够轻松地生成一系列的数字

    Python3 range() 函数返回的是一个可迭代对象(类型是对象),而不是列表类型, 所以打印的时候不会打印列表;

    Python3 list() 函数是对象迭代器,可以把range()返回的可迭代对象转为一个列表,返回的变量类型为列表;

    Python2 range() 函数返回的是列表

    例一:

    1
    2
    for i in range(1,5):
    print(i)

    输出结果如下:

    1
    2
    3
    4
    1
    2
    3
    4

    例二:

    1
    2
    for i in range(5):
    print(i)

    输出结果如下:

    1
    2
    3
    4
    5
    0
    1
    2
    3
    4

    例三:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    >>> list(range(5))
    [0, 1, 2, 3, 4]
    >>> list(range(0))
    []
    >>>list(range(0, 30, 5))
    [0, 5, 10, 15, 20, 25]
    >>> list(range(0, 10, 2))
    [0, 2, 4, 6, 8]
    >>> list(range(0, -10, -1))
    [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]
    >>> list(range(1, 0))
    []

    例四:

    1
    2
    3
    4
    5
    squares = []
    for value in range(1,11):
    square = value ** 2
    squares.append(square)
    print(squares)

    输出结果如下:

    1
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    - 3.2.1 对数字列表执行简单的统计计算

    1
    2
    3
    4
    5
    6
    7
    >>> digits = [1, 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0]
    >>> min(digits)
    0
    >>>max(digits)
    9
    >>>sum(digits)
    45

    - 3.2.2 列表解析

    列表解析能够让比如3.2中的例四更加简化,只需要一行代码就能生成这样的列表,列表解析将for循环和创建新元素的代码合并成一行,并自动附加新元素:

    1
    2
    squares = [value ** 2 for value in range(1,11)]
    print(squares)

    在这个示例中,for循环为for value in range(1,11),它将值1~10提供给表达式value ** 2
    输出结果如下:

    1
    [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

    - 3.3 使用列表的一部分

    处理列表的部分元素——Python称之为切片

    - 3.3.1 切片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    list = ['a','b','c','d','e','f']
    print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表
    print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2
    print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3
    print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾
    print(list[4:1]) #从左到右索引,因此为空值
    print(list[-1:-3]) #从左到右索引,因此为空值
    print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个
    print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2
    print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2
    print(list[::-1]) #反向取值,反向输出列表

    - 3.3.2 遍历列表

    1
    2
    3
    4
    players = ['charles' , 'martina' , 'michael' , 'florence' , 'eli']
    print("Here are the first three players on my team:")
    for player in players[:3]:
    print(player.title())

    输出结果如下:

    1
    2
    3
    4
    Here are the first three players on my team:
    Charles
    Martina
    Michael

    - 3.3.3 复制列表

    要复制列表,可以创建一个包含整个列表的切片,方法是同时省略起始索引和终止索引([:]),这让Python创建一个始于第一个元素,终止于最后一个元素的切片,即复制整个列表:

    1
    2
    3
    4
    5
    6
    my_foods = ['pizza' , 'falafel' , 'carrot cake']
    friend_foods = my_foods[:]
    print("My favorite foods are:")
    print(my_foods)
    print("\nMy friend's favorite foods are:")
    print(friend_foods)

    输出结果如下:

    1
    2
    3
    4
    5
    My favorite foods are:
    ['pizza', 'falafel', 'carrot cake']

    My friend's favorite foods are:
    ['pizza', 'falafel', 'carrot cake']

    为核实我们的确有两个列表,下面在每个列表中都添加一种食品,并核实每个列表都记录了相应人员喜欢的食品:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    my_foods = ['pizza' , 'falafel' , 'carrot cake']
    friend_foods = my_foods[:]

    my_foods.append('cannoli')
    friend_foods.append('ice cream')

    print("My favorite foods are:")
    print(my_foods)
    print("\nMy friend's favorite foods are:")
    print(friend_foods)

    输出结果如下:

    1
    2
    3
    4
    5
    My favorite foods are:
    ['pizza', 'falafel', 'carrot cake', 'cannoli']

    My friend's favorite foods are:
    ['pizza', 'falafel', 'carrot cake', 'ice cream']

    输出结果表明,’cannoli’包含在我喜欢的食品列表中,而’ice cream’没有;’ice cream’包含在我朋友喜欢的食品中,而’cannoli’没有,假如我们只是简单的将my_foods赋给friend_foods,就不能得到两个列表。下面是错误示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    my_foods = ['pizza' , 'falafel' , 'carrot cake']
    friend_foods = my_foods #错误写法

    my_foods.append('cannoli')
    friend_foods.append('ice cream')

    print("My favorite foods are:")
    print(my_foods)
    print("\nMy friend's favorite foods are:")
    print(friend_foods)

    错误示例输出结果如下:

    1
    2
    3
    4
    5
    My favorite foods are:
    ['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']

    My friend's favorite foods are:
    ['pizza', 'falafel', 'carrot cake', 'cannoli', 'ice cream']

    - 3.4 元组

    Python将不能修改的值称为不可变的,而不可变的列表被称为元组

    - 3.4.1 定义元组

    元组看起来就像是列表,但元组使用圆括号而不是方括号来标识,定义元组后,就可以使用索引来访问其元素,就像访问列表元素一样:

    1
    2
    3
    dimensions = (200,50)
    print(dimensions[0])
    print(dimensions[1])

    输出结果如下:

    1
    2
    200
    50

    如果尝试修改元组中元素的值,将会导致Python返回类型错误消息,由于试图修改元组的操作是被禁止的,因此Python指出不能给元组的元素赋值:

    1
    2
    dimensions = (200,50)
    dimensions[0] = 300

    将会报错:

    1
    2
    3
    4
    Traceback (most recent call last):
    File "dimensions.py", line 2, in <module>
    dimensions[0] = 300
    TypeError: 'tuple' object does not support item assignment

    - 3.4.2 遍历元组中所有的值

    像列表一样,元组也可以使用for循环来遍历元组中的所有值:

    例一:

    1
    2
    3
    dimensions = (200,100,50,6)
    for dimension in dimensions:
    print(dimension)

    输出结果如下:

    1
    2
    3
    4
    200
    100
    50
    6

    例二:

    1
    2
    3
    dimensions = (200,100,50,6)
    for dimension in dimensions[:3]:
    print(dimension)

    输出结果如下:

    1
    2
    3
    200
    100
    50

    - 3.4.3 修改元组变量

    虽然不能修改元组元素,但是可以给储存元组的变量赋值:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    dimensions = (200,50)
    print("Original dimensions:")
    for dimension in dimensions:
    print(dimension)

    dimensions = (400,100)
    print("\nModified dimensions:")
    for dimension in dimensions:
    print(dimension)

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    Original dimensions:
    200
    50

    Modified dimensions:
    400
    100

    我们首先定义了一个元组,并将其储存的尺寸打印了出来;然后将一个新元组储存到变量dimensions中,打印新的尺寸;相比于列表,元组是更简单的数据结构。如果需要储存的一组值在程序的整个生命周期内都不变,可使用元组

    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 操作列表 - - - -
    - - - - - Python3 基础学习笔记 C02 - - /2018/09/16/A08-Python3-basic-C02/ - -
    Python3 基础学习笔记第二章 —— 【列表】

    - 2.1列表是什么

    列表由一系列按特定顺序的元素组成,在 Python 中用方括号( [ ] )来表示列表,并用逗号来分隔其中的元素,例:

    1
    2
    3
    4
    5
    list1 = ['a','b','c','d','e','f']
    list2 = ['abc', 'xyz', 2018, 2020]
    list3 = [1, 2, 3, 4, 5 ,6]
    list4 = ["a", "b", "c", "d"]
    print(list1, list2, list3 ,list4)

    输出结果如下:

    1
    ['a', 'b', 'c', 'd', 'e', 'f'] ['abc', 'xyz', 2018, 2020] [1, 2, 3, 4, 5, 6] ['a', 'b', 'c', 'd']

    - 2.1.1访问列表元素

    列表是有序集合,因此要访问列表的元素,只需要将该元素的位置或索引告诉Python即可,注意:在Python中的第一个列表元素的索引为0,而不是1

    1
    2
    3
    4
    5
    list = ['a','b','c','d','e','f']
    print(list[0])
    print(list[3])
    print(list[-1]) #Python为访问最后一个列表元素提供了一种特殊语法,通过将索引指定为-1,可以让Python返回最后一个列表元素
    print(list[-3])

    输出结果如下:

    1
    2
    3
    4
    a
    d
    f
    d

    - 2.1.2列表切片

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    list = ['a','b','c','d','e','f']
    print(list[:]) #省略全部,代表截取全部内容,可以用来将一个列表拷给另一个列表
    print(list[:3]) #省略起始位置的索引,默认起始位置从头开始,结束位置索引为2
    print(list[3:]) #省略结束位置的索引,默认结束位置为最后一个,开始位置索引为3
    print(list[1:4]) #开始位置索引为1,结束位置索引为3,顾头不顾尾
    print(list[4:1]) #从左到右索引,因此为空值
    print(list[-1:-3]) #从左到右索引,因此为空值
    print(list[-3:-1]) #开始位置索引为倒数第三个,结束位置索引为倒数第二个
    print(list[1:5:2]) #开始位置索引为1,结束位置索引为4,间隔2
    print(list[5:1:-1]) #反向取值,开始位置索引为5,结束位置索引为2
    print(list[::-1]) #反向取值,反向输出列表

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'b', 'c']
    ['d', 'e', 'f']
    ['b', 'c', 'd']
    []
    []
    ['d', 'e']
    ['b', 'd']
    ['f', 'e', 'd', 'c']
    ['f', 'e', 'd', 'c', 'b', 'a']

    - 2.1.3使用列表中的各个值

    可像使用其他变量一样使用列表中的各个值,例如,我们可以使用拼接根据列表中的值来创建消息:

    1
    2
    3
    list = ['python', 'c', 'c++', 'java', 'php']
    message = "My favorite language is " + list[0].title() + "!"
    print(message)

    输出结果如下:

    1
    My favorite language is Python!

    - 2.1.4修改元素

    修改列表元素的语法与访问列表元素的语法类似,要修改列表元素,可指定列表名和要修改的元素的索引,再次指定该元素的新值:

    1
    2
    3
    4
    names = ['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']
    print(names)
    names[1] = 'lifang'
    print(names)

    输出结果如下:

    1
    2
    ['zhangsan', 'lishi', 'wanger', 'liming', 'xiaowang']
    ['zhangsan', 'lifang', 'wanger', 'liming', 'xiaowang']

    - 2.1.5添加元素

    - 使用方法 append() 在列表末尾添加元素
    1
    2
    3
    4
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    list.append('g')
    print(list)


    输出结果如下:

    1
    2
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'b', 'c', 'd', 'e', 'f', 'g']


    - 使用方法 insert() 在列表指定位置添加元素
    1
    2
    3
    4
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    list.insert(2,"h") #其中括号里的数字表示要插入的位置,此后面的元素将右移一个位置
    print(list)

    输出结果如下:

    1
    2
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'b', 'h', 'c', 'd', 'e', 'f', 'g']

    - 2.1.6删除元素

    - 使用 del 语句删除元素
    1
    2
    3
    4
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    del list[3]
    print(list)

    输出结果如下:

    1
    2
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    list = ['a', 'b', 'c', 'e', 'f']
    - 使用方法 pop() 删除最后一个元素
    方法 pop() 可以删除列表末尾的元素,并让你能够接着使用它。术语弹出(pop)源自这样的类比:列表就像是一个栈,而删除列表末尾的元素就相当于弹出栈顶元素:

    1
    2
    3
    4
    5
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    new_list = list.pop()
    print(list)
    print(new_list)


    输出结果如下:

    1
    2
    3
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'b', 'c', 'd', 'e']
    f


    - 使用方法 pop() 删除任意位置元素
    可以使用 pop() 来删除列表中任何位置的元素,只需要在括号中指定要删除的元素的索引即可:

    1
    2
    3
    4
    5
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    new_list = list.pop(1)
    print(list)
    print(new_list)


    输出结果如下:

    1
    2
    3
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'c', 'd', 'e', 'f']
    b


    - 使用方法 remove() 删除未知位置元素
    当我们不知道元素的位置,只知道元素的值的时候,就可以使用方法 remove()

    1
    2
    3
    4
    list = ['a', 'b', 'c', 'd', 'e', 'f']
    print(list)
    list.remove('d')
    print(list)


    输出结果如下:

    1
    2
    ['a', 'b', 'c', 'd', 'e', 'f']
    ['a', 'b', 'c', 'e', 'f']


    # - 2.1.7使用方法 index() 查找指定元素位置
    1
    2
    list = ["a", "b", "c", "d", "e", "a"]
    print(list.index('c'))

    输出结果如下:

    1
    2

    - 2.1.8使用方法 count() 统计指定元素数量

    1
    2
    list = ["a", "b", "c", "d", "e", "a"]
    print(list.count('a'))

    输出结果如下:

    1
    2

    - 2.1.9清空列表

    1
    2
    3
    list = ["a", "b", "c", "d", "e", "a"]
    list.clear()
    print(list)

    输出结果如下:

    1
    []

    - 2.2组织列表

    在创建的列表中,元素的排列顺序常常是无法预测的,因为我们并非总能控制用户提供数据的顺序。有时候,我们希望保留列表元素最初的排列顺序,而有时候又需要调整排列顺序。Python提供了很多组织列表的方式,可根据具体情况选用

    - 2.2.1使用方法 sort() 对列表进行永久排序

    使用方法 sort() 可以对列表按照特殊符号,数字,大写字母,小写字母顺序进行永久排序:

    1
    2
    3
    cars = ['bmw', 'audi', 'toyota', 'subaru']
    cars.sort()
    print(cars)

    输出结果如下:

    1
    ['audi', 'bmw', 'subaru', 'toyota']

    还可以按与字母顺序相反的顺序排列列表元素,只需要向 sort() 方法传递参数 reverse = True 就可以了:

    1
    2
    3
    cars = ['bmw', 'audi', 'toyota', 'subaru']
    cars.sort(reverse = True)
    print(cars)

    输出结果如下:

    1
    ['toyota', 'subaru', 'bmw', 'audi']

    - 2.2.2使用函数 sorted() 对列表进行临时排序

    要保留列表元素原来的排列顺序,同时以特定的顺序呈现它们,可使用函数sorted()。函数sorted()让你能够按特定顺序显示列表元素,同时不影响它们在列表中的原始排列顺序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    cars = ['bmw', 'audi', 'toyota', 'subaru']
    print("Here is the original list:")
    print(cars)
    print("\nHere is the sorted list:")
    print(sorted(cars))
    print("\nHere is the sorted reverse list:")
    print(sorted(cars, reverse=True))
    print("\nHere is the original list again:")
    print(cars)

    输出结果如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Here is the original list:
    ['bmw', 'audi', 'toyota', 'subaru']

    Here is the sorted list:
    ['audi', 'bmw', 'subaru', 'toyota']

    Here is the sorted reverse list:
    ['toyota', 'subaru', 'bmw', 'audi']

    Here is the original list again:
    ['bmw', 'audi', 'toyota', 'subaru']

    - 2.2.3使用方法 reverse() 对列表进行反向排序

    要反转列表元素的排列顺序,可使用方法 reverse()

    1
    2
    3
    cars = ['bmw', 'audi', 'toyota', 'subaru']
    cars.reverse()
    print(cars)

    输出结果如下:

    1
    ['subaru', 'toyota', 'audi', 'bmw']

    - 2.2.4确定列表的长度

    使用函数 len() 可以快速获悉列表的长度:

    1
    2
    3
    >>>cars = ['bmw', 'audi', 'toyota', 'subaru']
    >>>len(cars)
    4

    - 2.2.5合并列表

    - 使用方法 extend() 合并列表
    1
    2
    3
    4
    5
    list1 = [1, 2, 3, 4]
    list2 = ['a', 'b', 'c', 'd']
    list1.extend(list2) #将列表list2添加到list1当中去
    print(list1)
    print(list2)

    输出结果如下:

    1
    2
    [1, 2, 3, 4, 'a', 'b', 'c', 'd']
    ['a', 'b', 'c', 'd']
    - 使用 “+” 号合并列表
    1
    2
    3
    4
    list1 = [1, 2, 3, 4]
    list2 = ['a', 'b', 'c', 'd']
    print(list1 + list2)
    print(list2 + list1)

    输出结果如下:

    1
    2
    [1, 2, 3, 4, 'a', 'b', 'c', 'd']
    ['a', 'b', 'c', 'd', 1, 2, 3, 4]
    - 使用切片合并列表
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    list1 = [1, 2, 3, 4]
    list2 = ['a', 'b', 'c', 'd']
    list1[len(list1) : len(list1)] = list2 #len(list1)代表要将list2插入list1中的位置
    print(list1)

    list1 = [1, 2, 3, 4]
    list2 = ['a', 'b', 'c', 'd']
    list1[0 :0] = list2
    print(list1)

    list1 = [1, 2, 3, 4]
    list2 = ['a', 'b', 'c', 'd']
    list1[1:1] = list2
    print(list1)

    输出结果如下:

    1
    2
    3
    [1, 2, 3, 4, 'a', 'b', 'c', 'd']
    ['a', 'b', 'c', 'd', 1, 2, 3, 4]
    [1, 'a', 'b', 'c', 'd', 2, 3, 4]
    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 列表 - - - -
    - - - - - Python3 基础学习笔记 C01 - - /2018/09/13/A07-Python3-basic-C01/ - -
    Python3 基础学习笔记第一章 —— 【变量和简单数据类型】

    - 1.1变量的命名和使用

    变量名只能包含字母、数字和下划线。变量名可以字母或者下划线打头,但不能以数字开头,例如,可以将变量命名为message_1,但不能将其命名为1_message

    变量名不能包含空格,但可使用下划线来分割其中的单词,例如,变量名greeting_message可行,但变量名greeting message会引发错误

    不要将Python关键字和函数名用作变量名,即不要使用Python保留用于特殊用途的单词,如print

    变量名应既简短又具有描述性,例如,name比n好,student_name比s_n好,name_length比length_of_persons_name好

    慎用小写字母l和大写字母O,因为它们可能被人看错成数字1和0

    - 1.2字符串

    字符串就是一系列字符,在Python中,用引号括起来的都是字符串,其中的引号可以是单引号也可以双引号:

    1
    2
    "This is a string."
    'This is also a string.'

    这种灵活性让我们能够在字符串中包含引号和撇号:

    1
    2
    3
    'I told my friend,"Python is my favorite language!"'
    "The language 'Python' is named er Monty Python,not the snake."
    "One of Python's strengths is i diverse and supportive community."

    - 1.2.1使用方法修改字符串的大小写

    三种处理方法如下:

    1
    2
    3
    title()     #将字符串每个单词的首字母都改为大写
    upper() #将字符串的每个字母都改为大写
    lower() #将字符串的每个字母都改为小写

    例如:

    1
    2
    3
    4
    message = "I love you!"
    print(name.title())
    print(name.upper())
    print(name.lower())

    输出结果如下:

    1
    2
    3
    I Love You!
    I LOVE YOU!
    i love you!

    - 1.2.2合并(拼接)字符串

    Python使用加号(+)来合并字符串,举例说明:

    1
    2
    3
    4
    5
    first_name = "I"
    second_name = "love"
    third_name = "python"
    full_name = first_name + " " + second_name + " " + third_time
    print(full_name.title() + "!")

    输出结果如下:

    1
    I Love Python!

    - 1.2.3使用制表符或换行符来添加空白

    添加横向制表符:

    1
    2
    >>>print("\tPython")
    Python

    添加换行符:

    1
    2
    3
    4
    5
    >>>print("C\nC++\nPython\nJavaScript")
    C
    C++
    Python
    JavaScript
    附表:Python转义符



    - 1.2.4删除空白

    在Python中可用 lstrip()、rstrip()、strip() 分别删除字符串开头、结尾、全部的空白,举例说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    >>>message = ' python '
    >>>message
    ' python '
    >>>message.lstrip()
    'python '
    >>>message.rstrip()
    ' python'
    >>>message.strip()
    'python'

    如果要永久删除字符串中的空白,必须将删除操作的结果存回到变量中:

    1
    2
    3
    4
    >>>message = ' python '
    >>>message = message.strip()
    >>>message
    'python'

    - 1.3数字

    在编程中,经常使用数字来记录游戏得分、表示可视化数据、储存Web应用信息等。Python根据数字的用法以不同的方式处理它们

    - 1.3.1整数

    在Python中,可对整数执行加(+)减(-)乘(*)除(/)乘方(**)运算,同时也支持运算次序:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    >>>3 + 2
    5
    >>>3 - 2
    1
    >>>3 * 2
    6
    >>>3 \ 2
    1.5
    >>>3 ** 2
    9
    >>>3 ** 3
    27
    >>>2 + 3 * 4
    14
    >>>(2 + 3) * 4
    20

    - 1.3.2浮点数

    Python将带小数点的数字都称为浮点数:

    1
    2
    3
    4
    >>>0.1 + 0.1
    0.2
    >>>2 * 0.2
    0.4

    需要注意的是,结果包含的小数位可能是不确定的,就现在而言,暂时忽略多余的小数位即可:

    1
    2
    3
    4
    >>>0.2 + 0.1
    0.30000000000000004
    >>>3 * 0.1
    0.30000000000000004

    - 1.3.3使用函数 str() 避免错误

    错误例子:

    1
    2
    3
    age = 23
    message = "Happy " + age + "rd Birthday!"
    print(message)

    运行时会报错:

    1
    2
    3
    4
    Traceback (most recent call last):
    File "birthday.py", line 2, in <module>
    message = "Happy " + age + "rd Birthday!"
    TypeError: must be str, not int

    这是一个类型错误,意味着Python无法识别我们使用的信息。在这个例子中,Python发现我们使用了一个值为整数(int)的变量,但它不知道该如何解读这个值,这个变量表示的可能是数值23,也可能是字符2和3。像上面这样的字符串中使用整数时,需要显式地指出我们希望Python将这个整数用作字符串。为此,可调用函数 str(),它让Python将非字符串值表示为字符串:

    1
    2
    3
    age = 23
    message = "Happy " + str(age) + "rd Birthday!"
    print(message)

    输出结果如下:

    1
    Happy 23rd Birthday!

    - 1.4注释

    注释让我们能够使用自然语言在程序中添加说明,Python中注释有三种方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    print("Hello Python!")

    #这是单行注释

    '''这是多行注释
    这是多行注释'''

    """这也是多行注释
    这也是多行注释"""
    ]]>
    - - - - - Python3 学习笔记 - - 基础学习 - - - - - - - 数据类型 - - 变量 - - - -
    - - - - - VMware Pro 14 安装 Ubuntu 18.04 详细教程 - - /2018/09/09/A06-install-ubuntu18.04/ - -

    1.下载安装 VMware Workstation Pro 14

    进入 VMware 官网或者在软件商店下载最新版VMware虚拟机并安装

    01

    2.下载 Ubuntu 18.04 系统

    进入 Ubuntu 官网,下载最新版 Ubuntu 系统镜像

    02
    03

    3.在 VMware 中创建虚拟机

    打开安装好的 VMware Workstation Pro 14,选择创建新的虚拟机
    04

    在新建虚拟机向导中选择自定义(高级)
    05

    默认直接下一步,直到出现下图,再选择稍后安装操作系统
    06

    选择客户机操作系统为 Linux ,如果你电脑是32位就选择 Ubuntu 版本,64位就选择 Ubuntu 64 位版本
    07

    更改虚拟机名称及存放位置
    08

    为虚拟机指定处理器数量,默认即可
    09

    为虚拟机分配内存,太大了可能会导致卡顿,太小了也不好,推荐内存大小即可
    10

    以下均选择默认即可
    11
    13
    14

    选择创建新虚拟磁盘
    15

    选择将虚拟磁盘储存为单个文件
    16

    默认下一步
    17

    点击完成
    18

    此时我们就可以在虚拟机左侧“我的计算机”下面看到刚刚创建的虚拟机 Ubuntu 64 位,单击 Ubuntu 64 位,选择“编辑虚拟机设置”, 再选择“CD/DVD(SATA)”,选择“使用ISO映像文件”,点击“浏览”,找到先前我们下载好的 Ubuntu 64 位镜像文件,点击“确定”
    19
    20

    4.在虚拟机上安装 Ubuntu 系统

    单击 Ubuntu 64 位,选择“开启此虚拟机”
    21

    来到欢迎界面,选择好语言,点击“安装 Ubuntu”
    22

    选择键盘布局为“汉语”
    23

    更新和其他软件默认选择即可
    24

    安装类型选择“清除整个磁盘并安装 Ubuntu”,PS: 因为我们是新安装的系统,且在虚拟机中,所以可以选择清除整个磁盘,这个操作不会清除你原来电脑里面的东西
    25
    26

    地区随便,在中国就行,默认即可
    27

    之后设置计算机名,密码
    28

    点击继续稍等一会就安装完成啦
    29


    安装过程中可能会出现的一些问题


    1.在虚拟机上安装 Ubuntu 系统的过程中卡死不动

    解决方法:关闭网络,重新安装即可


    2.Ubuntu 不能全屏显示

    解决方法:
    方法①:安装 open-vm-tools:

    1
    sudo apt-get install open-vm-tools

    然后执行:

    1
    sudo apt-get install open-vm*

    重启即可全屏显示

    方法②:在终端输入xrandr,并回车,我们就可以看到很多可以修改的分辨率,选择好分辨率后,比如我们要修改分辨率为 1920x1440 ,则在终端输入 xrandr -s 1920x1440,回车即可,注意 1920x1440 中间是小写字母 x,本人亲测此方法并不是很完美,不能完全适应屏幕
    30

    方法③:安装 VMware Tools:
    1、进入 Ubuntu 系统后,点击虚拟机上的【虚拟机】—>【安装 VMware Tools】,回到桌面即可看到一个 VMware Tools 的 图标
    2、复制 VMwareTools-10.0.10-4301679.tar.gz(版本根据自己的实际情况而定)到 home 目录下, 用命令 tar -xzvf VMwareTools-10.0.10-4301679.tar.gz 进行解压
    3、解压后 cd vmware_tools_distrib,打开终端
    4、输入“sudo ./vmware-install.pl”,输入用户密码后开始安装
    5、接下来会有很多地方需要你按 Enter或者 Yes
    6、当你看到出现 —the vmware team 的字样后就可以关闭窗口了,此时窗口就会自动全屏了,如果没有全屏,重启过后就可以了
    7、若还没有全屏显示,则将虚拟机的【查看】—>【自动调整大小】—>【自适应客户机】,都选上,即可实现全屏

    ]]>
    - - - - - Linux - - - - - - - VMware - - Ubuntu - - - -
    - - - - - 主流 Markdown 编辑器推荐 - - /2018/08/29/A05-markdown-editor/ - -
    Markdown ,2004年由 John Gruberis 设计和开发,是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式,以下将介绍目前比较流行的一些 Markdown 编辑器(排名不分先后)

    - MarkdownPad

    目前分为 MarkdownPad2 和 MarkdownPad Pro 版本,后者收费,我们使用前者足矣,用户可以通过键盘快捷键和工具栏按钮来使用或者移除 Markdown 各种语法格式,支持自定义配色方案、字体、大小和布局 、即时HTML预览、HTML和PDF导出,被很多人称赞为 Windows 平台最好用的 Markdown 编辑器,实用性强,仅支持 Windows 系统,个人觉得在 Windows 10 系统上界面并不是很好看,有时候添加音乐什么的,资源多了,实时预览会显示资源加载失败,点击此处访问 MarkdownPad 官网

    MarkdownPad 2


    - BookPad

    无意间在 Microsoft Store 上发现的,完美搭配 Win10 系统,界面非常简洁漂亮,2017年9月份发布,大小30.82 MB,官方网站:https://sosfos.wordpress.com/ ,收费13人民币,可免费使用7天,各种功能应有尽有,和其他编辑器不相上下,本来想着百度百度看看有没有破解版,结果全网看不见 BookPad 的影子,估计是新出来的还不为人所知吧,可以直接在 Microsoft Store 搜索下载,或者点击链接获取:https://www.microsoft.com/store/apps/9N6P5ZH2SJSX

    BookPad

    BookPad2


    - 小书匠

    分为免费版和收费版,收费版¥20/年,其实免费版的功能已经足够强大了,多种编辑模式、多种主题选择、多种编辑器实现、丰富的语法支持、第三方同步、强大的文件管理功能,让人使用一次就爱上了它,支持 Windows 和 Web,推荐使用,点击此处访问小书匠官网

    小书匠
    小书匠2


    - Typora

    Typora 同样支持 Windows、OS X 和 Linux,Typora 支持即时渲染技术,这也是与其他 Markdown 编辑器最显著的区别,支持数学编辑,可与 Word 直接格式转换,在 Pandoc 的支持下进行多种文档格式转换,Typora 适合那些对码字手速和排版顺畅度有要求的人群,譬如码农、网站小编等,点击此处访问 Typora 官网

    Typora


    - Visual Studio Code

    Visual Studio Code 是众所周知的神器,是微软推出一款轻量级的文本编辑工具,类似于 Sublime,它已经默认集成 Markdown 文档编辑插件,原生就支持高亮 Markdown 的语法,但想要实时预览还需要选择 Markdown: Open Preview to the Side 命令实现,相关教程请点击此处点击此处 访问 Visual Studio Code 官网

    Visual Studio Code


    - Marxico

    Marxico 中文名马克飞象,提供桌面客户端以及离线 Chrome App,支持移动端 Web,可以直接把文本存到印象笔记,点击此处访问 Marxico,点击此处访问 马克飞象

    马克飞象


    - Sublime Text 3

    Sublime Text 3 是基于 Vim 开发的跨平台代码编辑器,收费80美元,好像可以免费试用,支持 OS X、Windows、Ubuntu 等 UNIX 及 Linux 操作系统,由于其功能的多样性而广受好评,界面简约大方,定位专业,原生支持的编程语言就多达十几种,通过第三方插件,还能实现更多语法的支持,其中就包括 Markdown ,但也有个缺点,就是不能实时预览,但是用户可以通过 Markdown Preview 的插件实现对 Markdown 的预览,具体教程请点击此处查看,点击此处访问 Sublime Text 官网

    SublimeText


    - Mou

    Mou 是一款由国人独立开发者罗晨开发的实时预览型 Markdown 编辑器,仅支持 OS X操作系统,是目前同类应用中对汉字兼容性最好的作品,也是目前最好用的免费 Markdown 编辑器,提供语法高亮、在线预览、同步滚动、全屏模式,支持自定保存、自动匹配,允许自定义主题,支持 CSS,HTML 和 PDF 导出等功能,点击此处访问 Mou 官网

    Mou


    - Atom

    Atom 是 Github 专门为程序员推出的一个跨平台文本编辑器,具有简洁和直观的图形用户界面,并有很多有趣的特点:支持CSS,HTML,JavaScript等网页编程语言,当然也支持 Markdown ,支持宏,自动完成分屏功能,集成了文件管理器,点击此处访问 Atom 官网

    Atom


    - Smark

    国人编写的开源软件,Windows / Linux 等主流系统跨平台支持,完美支持 LaTex 数学公式、脚注、尾注等,支持使用本地 MathJax 调用,不需要在线访问 MathJax CDN,用户可配置的 Markdown 语法高亮显示,美观整洁,多种格式文件导出支持,简洁友好的界面布局,完备的各类快捷键,能极大地提高工作效率,点击此处访问 Smark 官网

    Smark


    - Haroopad

    Haroopad 覆盖三大主流桌面系统,支持 Windows、OS X 和 Linux,多种主题样式供你选择,语法标亮支持 54 种编程语言,该工具重点推荐 Ubuntu/Linux 用户使用,点击此处访问 Haroopad 官网

    Haroopad


    - CuteMarkEd

    CuteMarkEd 是一个基于qt5的跨平台的 Markdown 编辑器,开源的, 提供实时 HTML 预览、数学表达式、源码高亮和PDF导出,点击此处 访问 CuteMarkEd 官网

    CuteMarkEd


    - MarkPad

    MarkPad 是款开源的 Markdown 编辑器,与 Window 8 风格和谐友好的界面,可以直接在你的博客或者 GitHub 中打开、保存文档,直接将图片粘贴到 Markdown 文档中,点击此处访问 MarkPad 官网

    MarkPad


    - Cmd Markdown

    作业部落出品,是一款不错的工具和博客平台兼顾的产品,同时支持 Linux、Mac 和 Windows 操作系统,此外还提供 Web 在线创作,社交化批注、智能云同步,最简单的方法,满足多种写作需要,点击此处访问 Cmd Markdown 官网

    Cmd Markdown


    - FarBox

    同样是一款不错的 Markdown 编辑器和博客平台兼顾的产品,让用户通过Dropbox(现在默认是自己的同步服务器)直接建立个人网站。FarBox编辑器免费,同时支持 Linux、Mac 和 Windows 操作系统,Farbox服务可以免费试用,在本地编辑器内写作自动同步发布在个人博客,对于希望有个人博客但却不愿折腾的小白来说,是个不错的选择,点击此处访问 FarBox 官网

    FarBox


    - Miu

    Miu 是一款 Windows 下的 Markdown 编辑器,支持 Markdown 高亮、代码高亮、即时预览,以及可以快速发布到 Github Gist,小众软件,界面美观,已经找不到官网了,小众软件网有提供百度云下载,Miu 下载地址

    Miu


    - MacDown

    MacDown 引用了许多 Mou 的设计方式,仅支持 Mac ,开源免费,点击此处访问 MacDown 官网

    MacDown


    - Ulysses

    一款由国外开发商 The Soulmen 制作的 Markdown 编辑器。与其它同类应用相比,Ulysses 最大的不同在于,它能根据内置的文件管理器,以及与 iCloud 云服务器的实时同步方案,达到最快捷的文章整理效率,支持OS X , iPad,26人民币每月,14天免费试用,点击此处访问 Ulysses 官网

    Ulysses


    - Byword

    一款轻量级的 Markdown 编辑器,支持Mac,iPhone和iPad,界面极简,功能强大,貌似要付费使用,点击此处 访问 Byword 官网

    Byword


    - MaHua

    一个在线编辑 Markdown 文档的编辑器,小众软件,VIM 快捷键支持,完美兼容 Github 的 Markdown 语法,界面稍许简陋,点击此处访问 MaHua

    MaHua


    - Dillinger

    来自国外的 Markdown 编辑器,漂亮强大,支持md、 html、pdf 文件导出,支持Dropbox、Github、Google Drive、Onedrive 一键保存,点击此处访问 Dillinger

    Dillinger


    - CSDN

    中国专业IT社区CSDN (Chinese Software Developer Network) 创立于1999年,致力于为中国软件开发者提供知识传播、在线学习、职业发展等全生命周期服务。CSDN的在线编辑器功能强大,支持导出为HTML和md文件,注册账号后即可开始创作,点击此处访问CSDN官网

    CSDN


    - 简书

    简书是一个优质的创作社区,你可以在线创作并发表到社区,是国内优质原创内容输出平台,简书从一开始就已经支持 Markdown 和富文本编辑,是一个为专门为作者打造的平台,点击此处访问简书官网

    简书


    要细数 Markdown 编辑器的话,可能永远也数不尽,而且每个人的看法也不同,正所谓萝卜白菜各有所爱,什么编辑器不是最重要的,重要的是我们能写出优质的文章,不断学习进步!不断提升自我!

    参考资料:
    《好用的Markdown编辑器一览》(By:月光)
    《10款流行的Markdown编辑器,总有一款适合你》(By:xiaoxiao_engineer)
    《解决作者们的焦虑:7 款优秀 Markdown 编辑工具推荐》(By:JailJT)

    ]]>
    - - - - - Markdown - - - - - - - Markdown - - 编辑器 - - - -
    - - - - - Hexo 博客主题个性化 - - /2018/08/27/A04-Hexo-blog-topic-personalization/ - -

    本文将讲述一些博客主题的美化、实用功能的添加,本文以作者 luumanspfk 主题和作者 xaoxuuMaterial X 主题为例,文章会不定时进行更新。文章涉及有关参考资料、教程、链接如有侵权请联系我删除!

    本文在CSDN的链接:《Hexo 博客优化之博客美化》《Hexo 博客优化之实用功能添加》,Hexo 博客专栏,从前期搭建到后期美化,帮您解决常见问题:《Github/Coding Pages + Hexo》,对您有帮助就点个赞吧❤️

    请注意:不同主题可能方法有些不同,相同主题不同版本,配置方法也有所差异!

    博客美化前提条件:有一定的前端基础,了解 HTML、CSS、JS,了解 CSS 预处理语言 Sass、Less、Stylus,搞懂 hexo 的目录结构。

    博客美化通用步骤:选定主题,认真阅读主题文档,分析主题目录结构,了解每个文件是对应网页哪个部分的,认真阅读美化教程,美化教程本质上只为你提供核心代码和思路,具体代码要添加到哪个地方,需要你自己搞懂主题结构,添加到需要的、合适的位置!

    博客美化终极奥秘:创作第一,体验第二,避免繁杂,简洁为上!


    【01】添加评论系统

    主流的评论系统有很多,比如:网易云跟帖、多说、友言、畅言、来必力(LiveRe)、Disqus、Valine、Gitment等等,目前网易云跟帖、多说、友言都已经关闭了,还有些可能需要翻墙,比较麻烦,百度了一下,最后还是选择了来必力评论系统

    进入来必力官网,注册一个账号(注册时可能需要翻墙)


    001

    注册完毕之后,登录,进入安装页面,选择 City 免费版安装,安装之后你会得到一段代码


    002

    003

    004

    我们打开主题文件下的 _config.yml 文件,添加如下代码:


    005

    \themes\hexo-theme-spfk\layout\_partial\comments 文件夹下新建一个 livere.ejs 的文件,在里面填写来必力提供的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!-- 来必力City版安装代码 -->
    <div id="lv-container" data-id="city" data-uid="这里是你的uid">
    <script type="text/javascript">
    (function(d, s) {
    var j, e = d.getElementsByTagName(s)[0];

    if (typeof LivereTower === 'function') { return; }

    j = d.createElement(s);
    j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
    j.async = true;

    e.parentNode.insertBefore(j, e);
    })(document, 'script');
    </script>
    <noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
    </div>
    <!-- City版安装代码已完成 -->

    打开 \themes\hexo-theme-spfk\layout\_partial\article.ejs 文件,在适当位置添加如下红框中的代码:


    006

    完成以上操作之后,我们就可以使用来必力评论系统了


    007

    【02】添加卡通人物

    我在逛别人博客的时候偶然发现右下角居然有一个萌萌的卡通人物,还能根据你鼠标位置摇头,瞬间被吸引到了,赶紧也给自己博客添加一个吧!点击此处进入该项目地址

    输入如下命令获取 live2d :

    1
    $ npm install --save hexo-helper-live2d

    输入以下命令,下载相应的模型,将 packagename 更换成模型名称即可,更多模型选择请点击此处,各个模型的预览请访问原作者的博客

    1
    $ npm install packagename

    打开站点目录下的 _config.yml 文件,添加如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    live2d:
    enable: true
    scriptFrom: local
    model:
    use: live2d-widget-model-haruto #模型选择
    display:
    position: right #模型位置
    width: 150 #模型宽度
    height: 300 #模型高度
    mobile:
    show: false #是否在手机端显示

    设置好过后我们就拥有了一个卡通人物


    008

    【03】自定义鼠标指针样式

    \themes\material-x\source\less\_base.less 文件 body 样式里写入如下代码:

    1
    2
    3
    4
    5
    6
    body {
    cursor: url(https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur),auto;
    background-color: @theme_background;
    ......
    ......
    }

    鼠标指针可以用 Axialis CursorWorkshop 这个软件自己制作,不同主题具体放的文件有所不同,确保在博客主体 body 的 CSS 文件中即可,其中的鼠标指针链接可替换成自己的,首先尝试加载 https://cdn.jsdelivr.net/gh/TRHX/CDN-for-itrhx.com@2.1.6/images/mouse.cur ,如果该文件不存在或由于其他原因无效,那么 auto 会被使用,也就是自动默认效果,图片格式为.ico、.ani、.cur,建议使用.cur,如果使用.ani或者其他格式无效,原因是浏览器兼容问题,请阅读参考文档或者参考以下兼容表:

    浏览器最低版本格式
    Internet Explorer6.0.cur / .ani
    Firefox (Gecko), Windows and Linux1.5 (1.8).cur / .png / .gif / .jpg
    Firefox (Gecko)4.0 (2.0).cur / .png / .gif / .jpg / .svg
    Opera
    Safari (Webkit)3.0 (522-523).cur / .png / .gif / .jpg

    拓展阅读:《CSS 鼠标样式 cursor属性》 (By:歪脖先生的博客)


    【04】添加鼠标点击爱心效果

    \themes\hexo-theme-spfk\source\js 下新建文件 love.js,在 love.js 文件中添加以下代码:

    1
    !function(e,t,a){function n(){c(".heart{width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 500%;-moz-border-radius: 50%;position: fixed;}.heart:after{top: -5px;}.heart:before{left: -5px;}"),o(),r()}function r(){for(var e=0;e<d.length;e++)d[e].alpha<=0?(t.body.removeChild(d[e].el),d.splice(e,1)):(d[e].y--,d[e].scale+=.004,d[e].alpha-=.013,d[e].el.style.cssText="left:"+d[e].x+"px;top:"+d[e].y+"px;opacity:"+d[e].alpha+";transform:scale("+d[e].scale+","+d[e].scale+") rotate(45deg);background:"+d[e].color+";z-index:99999");requestAnimationFrame(r)}function o(){var t="function"==typeof e.onclick&&e.onclick;e.onclick=function(e){t&&t(),i(e)}}function i(e){var a=t.createElement("div");a.className="heart",d.push({el:a,x:e.clientX-5,y:e.clientY-5,scale:1,alpha:1,color:s()}),t.body.appendChild(a)}function c(e){var a=t.createElement("style");a.type="text/css";try{a.appendChild(t.createTextNode(e))}catch(t){a.styleSheet.cssText=e}t.getElementsByTagName("head")[0].appendChild(a)}function s(){return"rgb("+~~(255*Math.random())+","+~~(255*Math.random())+","+~~(255*Math.random())+")"}var d=[];e.requestAnimationFrame=function(){return e.requestAnimationFrame||e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(e){setTimeout(e,1e3/60)}}(),n()}(window,document);

    \themes\hexo-theme-spfk\layout\layout.ejs 文件末尾添加以下代码:

    1
    2
    <!-- 页面点击小红心 -->
    <script type="text/javascript" src="/js/love.js"></script>

    完成以上操作后,当我们点击鼠标的时候就可以看见爱心的特效了


    009

    【05】添加鼠标点击显示字体效果

    \themes\hexo-theme-spfk\source\js 下新建文件 click_show_text.js,在 click_show_text.js 文件中添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    var a_idx = 0;
    jQuery(document).ready(function($) {
    $("body").click(function(e) {
    var a = new Array
    ("富强", "民主", "文明", "和谐", "自由", "平等", "公正", "法治", "爱国", "敬业", "诚信", "友善");
    var $i = $("<span/>").text(a[a_idx]);
    a_idx = (a_idx + 1) % a.length;
    var x = e.pageX,
    y = e.pageY;
    $i.css({
    "z-index": 5,
    "top": y - 20,
    "left": x,
    "position": "absolute",
    "font-weight": "bold",
    "color": "#FF0000"
    });
    $("body").append($i);
    $i.animate({
    "top": y - 180,
    "opacity": 0
    },
    3000,
    function() {
    $i.remove();
    });
    });
    setTimeout('delay()', 2000);
    });

    function delay() {
    $(".buryit").removeAttr("onclick");
    }

    其中的社会主义核心价值观可以根据你自己的创意替换为其他文字

    如果想要每次点击显示的文字为不同颜色,可以将其中 color 值进行如下更改:

    1
    "color": "rgb(" + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + "," + ~~(255 * Math.random()) + ")"

    然后在 \themes\hexo-theme-spfk\layout\layout.ejs 文件末尾添加以下代码:

    1
    2
    <!--单击显示文字-->
    <script type="text/javascript" src="/js/click_show_text.js"></script>

    最终实现效果如下:


    010

    【06】添加鼠标点击烟花爆炸效果

    \themes\material-x\source\js 目录下新建一个 fireworks.js 的文件,里面写入以下代码:

    1
    "use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)}"use strict";function updateCoords(e){pointerX=(e.clientX||e.touches[0].clientX)-canvasEl.getBoundingClientRect().left,pointerY=e.clientY||e.touches[0].clientY-canvasEl.getBoundingClientRect().top}function setParticuleDirection(e){var t=anime.random(0,360)*Math.PI/180,a=anime.random(50,180),n=[-1,1][anime.random(0,1)]*a;return{x:e.x+n*Math.cos(t),y:e.y+n*Math.sin(t)}}function createParticule(e,t){var a={};return a.x=e,a.y=t,a.color=colors[anime.random(0,colors.length-1)],a.radius=anime.random(16,32),a.endPos=setParticuleDirection(a),a.draw=function(){ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.fillStyle=a.color,ctx.fill()},a}function createCircle(e,t){var a={};return a.x=e,a.y=t,a.color="#F00",a.radius=0.1,a.alpha=0.5,a.lineWidth=6,a.draw=function(){ctx.globalAlpha=a.alpha,ctx.beginPath(),ctx.arc(a.x,a.y,a.radius,0,2*Math.PI,!0),ctx.lineWidth=a.lineWidth,ctx.strokeStyle=a.color,ctx.stroke(),ctx.globalAlpha=1},a}function renderParticule(e){for(var t=0;t<e.animatables.length;t++){e.animatables[t].target.draw()}}function animateParticules(e,t){for(var a=createCircle(e,t),n=[],i=0;i<numberOfParticules;i++){n.push(createParticule(e,t))}anime.timeline().add({targets:n,x:function(e){return e.endPos.x},y:function(e){return e.endPos.y},radius:0.1,duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule}).add({targets:a,radius:anime.random(80,160),lineWidth:0,alpha:{value:0,easing:"linear",duration:anime.random(600,800)},duration:anime.random(1200,1800),easing:"easeOutExpo",update:renderParticule,offset:0})}function debounce(e,t){var a;return function(){var n=this,i=arguments;clearTimeout(a),a=setTimeout(function(){e.apply(n,i)},t)}}var canvasEl=document.querySelector(".fireworks");if(canvasEl){var ctx=canvasEl.getContext("2d"),numberOfParticules=30,pointerX=0,pointerY=0,tap="mousedown",colors=["#FF1461","#18FF92","#5A87FF","#FBF38C"],setCanvasSize=debounce(function(){canvasEl.width=2*window.innerWidth,canvasEl.height=2*window.innerHeight,canvasEl.style.width=window.innerWidth+"px",canvasEl.style.height=window.innerHeight+"px",canvasEl.getContext("2d").scale(2,2)},500),render=anime({duration:1/0,update:function(){ctx.clearRect(0,0,canvasEl.width,canvasEl.height)}});document.addEventListener(tap,function(e){"sidebar"!==e.target.id&&"toggle-sidebar"!==e.target.id&&"A"!==e.target.nodeName&&"IMG"!==e.target.nodeName&&(render.play(),updateCoords(e),animateParticules(pointerX,pointerY))},!1),setCanvasSize(),window.addEventListener("resize",setCanvasSize,!1)};

    然后在 \themes\material-x\layout\layout.ejs 文件中写入以下代码:

    1
    2
    3
    <canvas class="fireworks" style="position: fixed;left: 0;top: 0;z-index: 1; pointer-events: none;" ></canvas> 
    <script type="text/javascript" src="//cdn.bootcss.com/animejs/2.2.0/anime.min.js"></script>
    <script type="text/javascript" src="/js/fireworks.js"></script>

    最终效果:


    011

    【07】添加彩色滚动变换字体

    在你想要添加彩色滚动变换字体的地方写入以下代码即可,其中文字可自行更改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    <div id="binft"></div>
    <script>
    var binft = function (r) {
    function t() {
    return b[Math.floor(Math.random() * b.length)]
    }
    function e() {
    return String.fromCharCode(94 * Math.random() + 33)
    }
    function n(r) {
    for (var n = document.createDocumentFragment(), i = 0; r > i; i++) {
    var l = document.createElement("span");
    l.textContent = e(), l.style.color = t(), n.appendChild(l)
    }
    return n
    }
    function i() {
    var t = o[c.skillI];
    c.step ? c.step-- : (c.step = g, c.prefixP < l.length ? (c.prefixP >= 0 && (c.text += l[c.prefixP]), c.prefixP++) : "forward" === c.direction ? c.skillP < t.length ? (c.text += t[c.skillP], c.skillP++) : c.delay ? c.delay-- : (c.direction = "backward", c.delay = a) : c.skillP > 0 ? (c.text = c.text.slice(0, -1), c.skillP--) : (c.skillI = (c.skillI + 1) % o.length, c.direction = "forward")), r.textContent = c.text, r.appendChild(n(c.prefixP < l.length ? Math.min(s, s + c.prefixP) : Math.min(s, t.length - c.skillP))), setTimeout(i, d)
    }
    var l = "",
    o = ["青青陵上柏,磊磊涧中石。", "人生天地间,忽如远行客。","斗酒相娱乐,聊厚不为薄。", "驱车策驽马,游戏宛与洛。","洛中何郁郁,冠带自相索。","长衢罗夹巷,王侯多第宅。","两宫遥相望,双阙百余尺。","极宴娱心意,戚戚何所迫?"].map(function (r) {
    return r + ""
    }),
    a = 2,
    g = 1,
    s = 5,
    d = 75,
    b = ["rgb(110,64,170)", "rgb(150,61,179)", "rgb(191,60,175)", "rgb(228,65,157)", "rgb(254,75,131)", "rgb(255,94,99)", "rgb(255,120,71)", "rgb(251,150,51)", "rgb(226,183,47)", "rgb(198,214,60)", "rgb(175,240,91)", "rgb(127,246,88)", "rgb(82,246,103)", "rgb(48,239,130)", "rgb(29,223,163)", "rgb(26,199,194)", "rgb(35,171,216)", "rgb(54,140,225)", "rgb(76,110,219)", "rgb(96,84,200)"],
    c = {
    text: "",
    prefixP: -s,
    skillI: 0,
    skillP: 0,
    direction: "forward",
    delay: a,
    step: g
    };
    i()
    };
    binft(document.getElementById('binft'));
    </script>

    最终效果:


    012

    【08】添加字数统计和阅读时长

    先在博客目录下执行以下命令安装 hexo-wordcount 插件:

    1
    $ npm i --save hexo-wordcount

    注意:在 Material X 主题中,字数统计和阅读时长的功能我已提交 PR,在最新版本中,只需要安装插件后,在主题 config.yml 配置文件里,将 word_count 关键字设置为 true 即可,对于旧版本,可以通过以下方法实现:

    Material X 主题(版本 1.2.1)为例,在 \themes\material-x\layout\_meta 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <% if(isPostList || !isPostList){ %>
    <% if (theme.word_count && !post.no_word_count) { %>
    <div style="margin-right: 10px;">
    <span class="post-time">
    <span class="post-meta-item-icon">
    <i class="fa fa-keyboard"></i>
    <span class="post-meta-item-text"> 字数统计: </span>
    <span class="post-count"><%= wordcount(post.content) %></span>
    </span>
    </span>
    &nbsp; | &nbsp;
    <span class="post-time">
    <span class="post-meta-item-icon">
    <i class="fa fa-hourglass-half"></i>
    <span class="post-meta-item-text"> 阅读时长≈</span>
    <span class="post-count"><%= min2read(post.content) %>分</span>
    </span>
    </span>
    </div>
    <% } %>
    <% } %>

    然后在主题的配置文件 _config.yml 找到 meta 关键字,将 word 填入 header 中:

    1
    2
    3
    meta:
    header: [title, author, date, categories, tags, counter, word, top]
    footer: [updated, share]

    最后在主题目录下的 _config.yml 添加以下配置即可

    1
    word_count: true

    效果图:


    036

    同样的,以 spfk 主题为例,在 \themes\hexo-theme-spfk\layout\_partial\post 目录下创建 word.ejs 文件,在 word.ejs 文件中写入以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <div style="margin-top:10px;">
    <span class="post-time">
    <span class="post-meta-item-icon">
    <i class="fa fa-keyboard-o"></i>
    <span class="post-meta-item-text"> 字数统计: </span>
    <span class="post-count"><%= wordcount(post.content) %></span>
    </span>
    </span>
    &nbsp; | &nbsp;
    <span class="post-time">
    <span class="post-meta-item-icon">
    <i class="fa fa-hourglass-half"></i>
    <span class="post-meta-item-text"> 阅读时长: </span>
    <span class="post-count"><%= min2read(post.content) %></span>
    </span>
    </span>
    </div>

    然后在 \themes\hexo-theme-spfk\layout\_partial\article.ejs 中适当位置添加以下代码:


    013

    最后在主题目录下的 _config.yml 添加以下配置

    1
    word_count: true

    如果显示的位置不好,可以自行更改其位置,成功配置后的效果如下:


    014

    015

    另外:要在博客底部显示所有文章的总字数,可以点击此处,根据你博客底部文件的类型选择相应的代码放在适当的位置即可,前提是要安装好 hexo-wordcount 插件,例如我使用 Material X 主题,在 \themes\material-x\layout\_partial 目录下的 footer.ejs 文件中添加如下代码:

    1
    2
    <i class="fas fa-chart-area"></i>
    <span class="post-count">字数统计:<%= totalcount(site) %></span>

    实现效果如下:


    016

    【09】添加背景音乐

    打开网页版网易云音乐,选择你准备添加的背景音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,复制底下的HTML代码


    017

    018

    然后将此代码放到你想要放的地方,比如放在博客的左侧,则打开 \themes\hexo-theme-spfk\layout\_partial\left-col.ejs 文件,将复制的HTML代码粘贴进去,再进行适当的位置设置让播放器更美观,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐


    019

    最后效果如下:


    020

    这种网易云音乐外链的方式有很多局限性,因此推荐使用aplayer,GitHub地址为:https://github.com/MoePlayer/APlayer ,参考教程:《hexo上的aplayer应用》


    【10】添加网站运行时间

    一个比较好的小功能,可以看见自己的博客运行多久了,时间一天天的增加,成就感也会一天天增加的
    \themes\hexo-theme-spfk\layout\_partial\footer.ejs 文件下添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span>
    <script>
    var now = new Date();
    function createtime() {
    var grt= new Date("08/10/2018 17:38:00");//在此处修改你的建站时间,格式:月/日/年 时:分:秒
    now.setTime(now.getTime()+250);
    days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
    hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours);
    if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;}
    seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;}
    document.getElementById("timeDate").innerHTML = "本站已安全运行 "+dnum+" 天 ";
    document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒";
    }
    setInterval("createtime()",250);
    </script>

    最后效果如下:


    021

    【11】添加百度统计

    百度统计是百度推出的一款免费的专业网站流量分析工具,能够告诉用户访客是如何找到并浏览用户的网站,在网站上做了些什么,非常有趣,接下来我们把百度统计添加到自己博客当中

    访问百度统计首页,注册一个账号后登陆,添加你的博客网站


    022

    接着点击代码获取,复制该代码


    023

    然后到目录 \Hexo\themes\hexo-theme-spfk\layout\_partial 下新建一个 baidu-analytics.ejs 文件,里面粘贴你刚刚复制的代码


    024

    修改主题文件夹下的 _config.yml 文件,将你的key(图中涂掉部分)填写进去:


    025

    所有操作完成后可以在百度统计管理页面检查代码是否安装成功,如果代码安装正确,一般20分钟后,可以查看网站分析数据


    026

    另外推荐:友盟,2010年4月在北京成立,安全、可靠、公正、第三方的网站流量统计分析系统


    【12】浏览器网页标题恶搞

    当用户访问你的博客时点击到了其他网页,我们可以恶搞一下网页标题,呼唤用户回来,首先在目录 \themes\material-x\source\js 下新建一个 FunnyTitle.js 文件,在里面填写如下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 浏览器搞笑标题
    var OriginTitle = document.title;
    var titleTime;
    document.addEventListener('visibilitychange', function () {
    if (document.hidden) {
    $('[rel="icon"]').attr('href', "/funny.ico");
    document.title = '╭(°A°`)╮ 页面崩溃啦 ~';
    clearTimeout(titleTime);
    }
    else {
    $('[rel="icon"]').attr('href', "/favicon.ico");
    document.title = '(ฅ>ω<*ฅ) 噫又好啦 ~' + OriginTitle;
    titleTime = setTimeout(function () {
    document.title = OriginTitle;
    }, 2000);
    }
    });

    其中 funny.ico 是用户切换到其他标签后你网站的图标,favicon.ico 是正常图标,然后在 \themes\material-x\layout\layout.ejs 文件中添加如下代码:

    1
    2
    <!--浏览器搞笑标题-->
    <script type="text/javascript" src="/js/FunnyTitle.js"></script>

    再次部署博客后就可以看见标题搞笑的效果了:


    027

    028

    【13】背景添加动态线条效果

    \Hexo\themes\hexo-theme-spfk\layout\layout.ejs 文件中添加如下代码:

    1
    2
    3
    4
    <!--动态线条背景-->
    <script type="text/javascript"
    color="220,220,220" opacity='0.7' zIndex="-2" count="200" src="//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js">
    </script>

    其中:

    • color:表示线条颜色,三个数字分别为(R,G,B),默认:(0,0,0)
    • opacity:表示线条透明度(0~1),默认:0.5
    • count:表示线条的总数量,默认:150
    • zIndex:表示背景的z-index属性,css属性用于控制所在层的位置,默认:-1

    最终实现效果:


    029

    【14】添加人体时钟

    无意中发现了个有趣的人体时钟 HONE HONE CLOCK,作者是个日本人,点击此处访问作者博客,点击此处在作者原博客上查看动态样式,点击此处查看动态大图,如果你的博客上有合适的地方,加上一个人体时钟会很有趣的


    030

    实现代码:

    1
    2
    3
    4
    5
    <!--人体时钟背景透明-->
    <script charset="Shift_JIS" src="http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_tr.js"></script>

    <!--人体时钟背景白-->
    <script charset="Shift_JIS" src="http://chabudai.sakura.ne.jp/blogparts/honehoneclock/honehone_clock_wh.js"></script>

    其他网页小挂件推荐:

    • http://abowman.com/ 里面有很多有趣的小挂件,可以养养鱼、龟、狗、仓鼠等各式各样的虚拟宠物,能根据你的鼠标指针位置移动,直接复制代码就可以用
    • http://www.revolvermaps.com/ 它提供网站访客地理信息,可以以2D、3D等形式显示
    • http://www.amazingcounters.com/ 免费网站计数器,有非常多的样式供你选择,可以设置计数器初始数值,可以设置按访问量计数,也可以按独立访问者计数
    • https://www.seniverse.com/widget/get 心知天气提供基于Web的免费天气插件,可以为你的网站添加一项简洁美观的天气预报功能,并自动适配PC和手机上的浏览

    【15】添加RSS订阅

    RSS订阅是站点用来和其他站点之间共享内容的一种简易方式,即Really Simple Syndication(简易信息聚合),如果不会使用,可以参见百度百科:https://baike.baidu.com/item/RSS%E8%AE%A2%E9%98%85/663114 ;首先我们安装feed插件,在本地hexo目录下右键git bash here,输入以下命令:

    1
    $ npm install hexo-generator-feed

    等待安装完成后,打开hexo目录下配置文件的_config.yml,在末尾添加以下配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # Extensions
    ## Plugins: http://hexo.io/plugins/
    #RSS订阅
    plugin:
    - hexo-generator-feed
    #Feed Atom
    feed:
    type: atom
    path: atom.xml
    limit: 20

    随后打开主题配置文件_config.yml,添加以下配置:

    1
    rss: /atom.xml

    至此,RSS订阅功能添加完成


    【16】添加网站雪花飘落效果

    样式一和样式二分别如下:


    031样式一

    032样式二

    实现方法:在 \Hexo\themes\hexo-theme-spfk\source\js 目录下新建一个 snow.js 文件,粘贴以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    /*样式一*/
    (function($){
    $.fn.snow = function(options){
    var $flake = $('<div id="snowbox" />').css({'position': 'absolute','z-index':'9999', 'top': '-50px'}).html('&#10052;'),
    documentHeight = $(document).height(),
    documentWidth= $(document).width(),
    defaults = {
    minSize: 10,
    maxSize: 20,
    newOn: 1000,
    flakeColor: "#AFDAEF" /* 此处可以定义雪花颜色,若要白色可以改为#FFFFFF */
    },
    options= $.extend({}, defaults, options);
    var interval= setInterval( function(){
    var startPositionLeft = Math.random() * documentWidth - 100,
    startOpacity = 0.5 + Math.random(),
    sizeFlake = options.minSize + Math.random() * options.maxSize,
    endPositionTop = documentHeight - 200,
    endPositionLeft = startPositionLeft - 500 + Math.random() * 500,
    durationFall = documentHeight * 10 + Math.random() * 5000;
    $flake.clone().appendTo('body').css({
    left: startPositionLeft,
    opacity: startOpacity,
    'font-size': sizeFlake,
    color: options.flakeColor
    }).animate({
    top: endPositionTop,
    left: endPositionLeft,
    opacity: 0.2
    },durationFall,'linear',function(){
    $(this).remove()
    });
    }, options.newOn);
    };
    })(jQuery);
    $(function(){
    $.fn.snow({
    minSize: 5, /* 定义雪花最小尺寸 */
    maxSize: 50,/* 定义雪花最大尺寸 */
    newOn: 300 /* 定义密集程度,数字越小越密集 */
    });
    });
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    /*样式二*/
    /* 控制下雪 */
    function snowFall(snow) {
    /* 可配置属性 */
    snow = snow || {};
    this.maxFlake = snow.maxFlake || 200; /* 最多片数 */
    this.flakeSize = snow.flakeSize || 10; /* 雪花形状 */
    this.fallSpeed = snow.fallSpeed || 1; /* 坠落速度 */
    }
    /* 兼容写法 */
    requestAnimationFrame = window.requestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    function(callback) { setTimeout(callback, 1000 / 60); };

    cancelAnimationFrame = window.cancelAnimationFrame ||
    window.mozCancelAnimationFrame ||
    window.webkitCancelAnimationFrame ||
    window.msCancelAnimationFrame ||
    window.oCancelAnimationFrame;
    /* 开始下雪 */
    snowFall.prototype.start = function(){
    /* 创建画布 */
    snowCanvas.apply(this);
    /* 创建雪花形状 */
    createFlakes.apply(this);
    /* 画雪 */
    drawSnow.apply(this)
    }
    /* 创建画布 */
    function snowCanvas() {
    /* 添加Dom结点 */
    var snowcanvas = document.createElement("canvas");
    snowcanvas.id = "snowfall";
    snowcanvas.width = window.innerWidth;
    snowcanvas.height = document.body.clientHeight;
    snowcanvas.setAttribute("style", "position:absolute; top: 0; left: 0; z-index: 1; pointer-events: none;");
    document.getElementsByTagName("body")[0].appendChild(snowcanvas);
    this.canvas = snowcanvas;
    this.ctx = snowcanvas.getContext("2d");
    /* 窗口大小改变的处理 */
    window.onresize = function() {
    snowcanvas.width = window.innerWidth;
    /* snowcanvas.height = window.innerHeight */
    }
    }
    /* 雪运动对象 */
    function flakeMove(canvasWidth, canvasHeight, flakeSize, fallSpeed) {
    this.x = Math.floor(Math.random() * canvasWidth); /* x坐标 */
    this.y = Math.floor(Math.random() * canvasHeight); /* y坐标 */
    this.size = Math.random() * flakeSize + 2; /* 形状 */
    this.maxSize = flakeSize; /* 最大形状 */
    this.speed = Math.random() * 1 + fallSpeed; /* 坠落速度 */
    this.fallSpeed = fallSpeed; /* 坠落速度 */
    this.velY = this.speed; /* Y方向速度 */
    this.velX = 0; /* X方向速度 */
    this.stepSize = Math.random() / 30; /* 步长 */
    this.step = 0 /* 步数 */
    }
    flakeMove.prototype.update = function() {
    var x = this.x,
    y = this.y;
    /* 左右摆动(余弦) */
    this.velX *= 0.98;
    if (this.velY <= this.speed) {
    this.velY = this.speed
    }
    this.velX += Math.cos(this.step += .05) * this.stepSize;

    this.y += this.velY;
    this.x += this.velX;
    /* 飞出边界的处理 */
    if (this.x >= canvas.width || this.x <= 0 || this.y >= canvas.height || this.y <= 0) {
    this.reset(canvas.width, canvas.height)
    }
    };
    /* 飞出边界-放置最顶端继续坠落 */
    flakeMove.prototype.reset = function(width, height) {
    this.x = Math.floor(Math.random() * width);
    this.y = 0;
    this.size = Math.random() * this.maxSize + 2;
    this.speed = Math.random() * 1 + this.fallSpeed;
    this.velY = this.speed;
    this.velX = 0;
    };
    // 渲染雪花-随机形状(此处可修改雪花颜色!!!)
    flakeMove.prototype.render = function(ctx) {
    var snowFlake = ctx.createRadialGradient(this.x, this.y, 0, this.x, this.y, this.size);
    snowFlake.addColorStop(0, "rgba(255, 255, 255, 0.9)"); /* 此处是雪花颜色,默认是白色 */
    snowFlake.addColorStop(.5, "rgba(255, 255, 255, 0.5)"); /* 若要改为其他颜色,请自行查 */
    snowFlake.addColorStop(1, "rgba(255, 255, 255, 0)"); /* 找16进制的RGB 颜色代码。 */
    ctx.save();
    ctx.fillStyle = snowFlake;
    ctx.beginPath();
    ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
    };
    /* 创建雪花-定义形状 */
    function createFlakes() {
    var maxFlake = this.maxFlake,
    flakes = this.flakes = [],
    canvas = this.canvas;
    for (var i = 0; i < maxFlake; i++) {
    flakes.push(new flakeMove(canvas.width, canvas.height, this.flakeSize, this.fallSpeed))
    }
    }
    /* 画雪 */
    function drawSnow() {
    var maxFlake = this.maxFlake,
    flakes = this.flakes;
    ctx = this.ctx, canvas = this.canvas, that = this;
    /* 清空雪花 */
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    for (var e = 0; e < maxFlake; e++) {
    flakes[e].update();
    flakes[e].render(ctx);
    }
    /* 一帧一帧的画 */
    this.loop = requestAnimationFrame(function() {
    drawSnow.apply(that);
    });
    }
    /* 调用及控制方法 */
    var snow = new snowFall({maxFlake:60});
    snow.start();

    然后在 \Hexo\themes\hexo-theme-spfk\layout\layout.ejs 文件里引用即可:

    1
    2
    <!-- 雪花特效 -->
    <script type="text/javascript" src="\js\snow.js"></script>

    如果没效果,请确认网页是否已载入JQurey,如果没有请在下雪代码之前引入JQ即可:

    1
    2
    <script type="text/javascript" src="http://libs.baidu.com/jquery/1.8.3/jquery.js"></script>
    <script type="text/javascript" src="http://libs.baidu.com/jquery/1.8.3/jquery.min.js"></script>

    原文链接:《分享两种圣诞节雪花特效JS代码(网站下雪效果)》


    【17】添加 Fork me on GitHub 效果

    效果图:


    033

    点击此处可以查看更多样式,将相应样式的代码复制到你想要放的地方就OK了,代码里的链接也要替换成你的,更多创意,比如 Follow me on CSDN ,只需要用PS改掉图片里的文字,替换掉相应链接即可


    【18】添加背景动态彩带效果

    样式一是鼠标点击后彩带自动更换样式,样式二是飘动的彩带:


    034

    实现方法:在 \themes\material-x\layout\layout.ejs 文件的body前面添加如下代码:

    1
    2
    <!-- 样式一(鼠标点击更换样式) -->
    <script src="https://g.joyinshare.com/hc/ribbon.min.js" type="text/javascript"></script>
    1
    2
    <!-- 样式二(飘动的彩带) -->
    <script src="https://g.joyinshare.com/hc/piao.js" type="text/javascript"></script>

    【19】添加背景代码雨特效

    新建 DigitalRain.js,写入以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    window.onload = function(){
    //获取画布对象
    var canvas = document.getElementById("canvas");
    //获取画布的上下文
    var context =canvas.getContext("2d");
    var s = window.screen;
    var W = canvas.width = s.width;
    var H = canvas.height;
    //获取浏览器屏幕的宽度和高度
    //var W = window.innerWidth;
    //var H = window.innerHeight;
    //设置canvas的宽度和高度
    canvas.width = W;
    canvas.height = H;
    //每个文字的字体大小
    var fontSize = 12;
    //计算列
    var colunms = Math.floor(W /fontSize);
    //记录每列文字的y轴坐标
    var drops = [];
    //给每一个文字初始化一个起始点的位置
    for(var i=0;i<colunms;i++){
    drops.push(0);
    }
    //运动的文字
    var str ="WELCOME TO WWW.ITRHX.COM";
    //4:fillText(str,x,y);原理就是去更改y的坐标位置
    //绘画的函数
    function draw(){
    context.fillStyle = "rgba(238,238,238,.08)";//遮盖层
    context.fillRect(0,0,W,H);
    //给字体设置样式
    context.font = "600 "+fontSize+"px Georgia";
    //给字体添加颜色
    context.fillStyle = ["#33B5E5", "#0099CC", "#AA66CC", "#9933CC", "#99CC00", "#669900", "#FFBB33", "#FF8800", "#FF4444", "#CC0000"][parseInt(Math.random() * 10)];//randColor();可以rgb,hsl, 标准色,十六进制颜色
    //写入画布中
    for(var i=0;i<colunms;i++){
    var index = Math.floor(Math.random() * str.length);
    var x = i*fontSize;
    var y = drops[i] *fontSize;
    context.fillText(str[index],x,y);
    //如果要改变时间,肯定就是改变每次他的起点
    if(y >= canvas.height && Math.random() > 0.99){
    drops[i] = 0;
    }
    drops[i]++;
    }
    };
    function randColor(){//随机颜色
    var r = Math.floor(Math.random() * 256);
    var g = Math.floor(Math.random() * 256);
    var b = Math.floor(Math.random() * 256);
    return "rgb("+r+","+g+","+b+")";
    }
    draw();
    setInterval(draw,35);
    };

    在主题文件的相关css文件中(以 Material X 1.2.1 主题为例,在\themes\material-x-1.2.1\source\less\_main.less 文件末尾)添加以下代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    canvas {
    position: fixed;
    right: 0px;
    bottom: 0px;
    min-width: 100%;
    min-height: 100%;
    height: auto;
    width: auto;
    z-index: -1;
    }

    然后在主题的 layout.ejs 文件中引入即可:

    1
    2
    3
    <!-- 数字雨 -->
    <canvas id="canvas" width="1440" height="900" ></canvas>
    <script type="text/javascript" src="/js/DigitalRain.js"></script>

    最终效果:


    035

    代码来源:http://www.lxl8800.cn/Main/Resource


    【20】自定义一个不使用主题模板渲染的独立页面

        有时候我们需要新建一个独立的页面,这个页面不使用主题的渲染,具有自己独立的样式,可以放一些自己的作品,相册什么的,以下就介绍这种独立页面的实现方法。

    方法一:

        使用 Hexo 提供的跳过渲染配置,在博客根目录的配置文件 _config.yml 里找到 skip_render 关键字,在后面添加想要跳过渲染的页面,比如我们创建 \source\about\index.html, 配置文件填写:skip_render: about\**,那么就表示 \source\about 里所有的文件将跳过渲染,里面的文件将会被直接复制到 public 文件夹,此时就会得到一个独立的 about 页面;官方文档:https://hexo.io/docs/configuration

    方法二:

        在文章头部的 Front-matter 里添加配置 layout: false 来跳过渲染配置,比如我们要使 about 页面跳过渲染,创建 \source\about\index.md,将这个页面的相关 HTML 代码写进.md文件并保存,然后在 index.md 的头部写入:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ---
    layout: false
    ---

    {% raw %}

    这里是 HTML 代码

    {% endraw %}

    PS:Front-matter 是 .md 文件最上方以 — 分隔的区域,用于指定个别文件的变量,官方文档:https://hexo.io/docs/front-matter

    效果可以对比我的博客主页关于页面


    【21】更改本地预览端口号

    hexo博客在执行 hexo s 进行本地预览的时候,默认端口号是4000,当该端口号被占用时会报错 Error: listen EADDRINUSE 0.0.0.0:4000 ,此时可以关闭占用该端口的进程,也可以更换端口号,更换端口号可以通过以下两种方法实现:

    方法一:在根目录的 _config.yml 配置文件内加上如下代码更改 hexo s 运行时的端口号:

    1
    2
    3
    4
    server:
    port: 5000
    compress: true
    header: true

    方法二:通过 hexo server -p 5000 命令来指定端口,这种方法只是本次执行有效


    未完待续……

    ]]>
    - - - - - Hexo - - - - - - - Hexo - - 主题个性化 - - Material X - - spfk - - - -
    - - - - - Markdown 语法&技巧总结 - - /2018/08/25/A03-markdown/ - -

    在写博客的时候,我们不希望都是千篇一律的没有色彩,多了解一些 Markdown 语法技巧有利于丰富我们的博客,看起来更有 feel !


    – 插入图片

    车

    如果你使用 MarkdownPad 的话就比较方便,可以直接选择插入本地图片或者是网络图片,实质是通过以下代码实现的,小括号里面就是你的图片地址,中括号里面是图片的替代文字,比如上面的图片代码如下:

    1
    ![](https://cdn.jsdelivr.net/gh/TRHX/ImageHosting/ITRHX-PIC/A03/01.jpg)

    – 插入音乐

    打开网页版网易云音乐,选择你准备插入的音乐,点击生成外链播放器,前提是要有版权,不然是无法生成外链播放器的,选择好尺寸后,复制底下的HTML代码

    然后将此HTML代码粘贴到你想要放的地方,可自行调节播放器的大小,其中 auto=1 表示打开网页自动播放音乐,auto=0 表示关闭自动播放音乐,比如See You Again (中英文版) - 罗艺恒这首歌曲代码如下:

    1
    <iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=32405683&auto=1&height=66"></iframe>

    02
    03

    – 插入视频

    高考毕业了我们为下一届的学弟学妹们录制高考加油视频,我担任后期制作,在这里就以该视频为例٩(๑❛ᴗ❛๑)۶,在腾讯视频播放页面找到分享按钮,复制该视频的通用代码(其他视频播放平台也一样),粘贴到文章中对应位置即可,可根据情况调整视频播放器的大小
    04

    1
    <iframe frameborder="0" width="840"  height="500" src="https://v.qq.com/txp/iframe/player.html?vid=x0643zvgtf7" allowFullScreen="true"></iframe>

    未完待续……


    ]]>
    - - - - - Markdown - - - - - - - Markdown - - 技巧 - - - -
    - - - - - 使用 Github Pages 和 Hexo 搭建自己的独立博客 - - /2018/08/15/A02-hexo-blog/ - -

    01

    – 前言

    首先感谢您能访问我的博客:TRHX’S BLOG

    这是一篇有关如何使用 Github PagesHexo 搭建属于自己独立博客的详尽教程,本人是软件工程专业本科生,目前只学习了C和C++编程语言,对网站开发的有关知识几乎为零,这也是我搭建好自己的博客之后写的第一篇博客,刚开始搭建博客的时候自己也是网上各种百度,由于自己属于小白那种,历经了千辛万苦才弄好,所以借这个机会写一篇小白真正能看懂的博客搭建教程,教你一步一步走向成功的彼岸!

    推荐文章: 《我为什么写博客》 (By 知明所以)
          《为什么你应该(从现在开始就)写博客》 (By 刘未鹏 | Mind Hacks)

    – 入门

    Github Pages

    Github Pages可以被认为是用户编写的、托管在github上的静态网页。使用Github Pages可以为你提供一个免费的服务器,免去了自己搭建服务器和写数据库的麻烦。此外还可以绑定自己的域名。

    Hexo

    Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。

    – 安装 Node.js

    点击此处访问官网,按需下载相应版本,默认安装可以了

    02

    注:本人在安装过程中出现了Warning 1909,无法创建快捷方式,这种情况很少出现,如果在安装过程中也有这种情况请参考百度文库(win10系统实测可行):《Win7安装程序警告1909无法创建快捷方式》

    03

    – 安装 Git

    点击此处访问官网,按需下载相应版本,默认安装即可
    参考资料:《如何在windows下安装GIT》  (By 俊雨廷休)
         《Pro Git(中文版)》

    – 检验软件是否安装成功

    同时按下 Win 键和 R 键打开运行窗口,输入 cmd ,然后输入以下命令,有相应版本信息显示则安装成功,若不正确可以卸载软件重新安装,此外若安装成功,在桌面右键鼠标,可以看到菜单里多了 Git GUI HereGit Bash Here两个选项,第一个是图形界面的Git操作,另一个是命令行

    1
    2
    3
    $ git --version
    $ node -v
    $ npm -v

    04

    05

    – Hexo 安装

    选择一个磁盘,新建一个文件夹,自己重命名文件夹(如:我的文件夹为:E\TRHX_Blog),博客相关文件将储存在此文件夹下,在该文件夹下右键鼠标,点击 Git Bash Here,输入以下 npm 命令即可安装,第一个命令表示安装 hexo,第二个命令表示安装 hexo 部署到 git page 的 deployer,如图所示即为安装成功

    1
    2
    $ npm install hexo-cli -g
    $ npm install hexo-deployer-git --save

    06

    – Hexo 初始化配置

    在刚才新建的文件夹里面再次新建一个 Hexo 文件夹(如:我的文件夹为:E\TRHX_Blog\Hexo),进入该 Hexo 文件夹右键鼠标,点击 Git Bash Here,输入以下命令,如图所示则安装成功

    1
    $ hexo init

    07

    Hexo 安装完成后,将会在指定文件夹中新建所需要的文件,Hexo 文件夹下的目录如下:
    08

    – 本地查看效果

    执行以下命令,执行完即可登录 http://localhost:4000/ 查看效果

    1
    2
    $ hexo generate
    $ hexo server

    显示以下信息说明操作成功:

    1
    INFO Hexo is running at http://0.0.0.0:4000/. Press Ctrl+C to stop.

    登录 http://localhost:4000/ 查看效果:
    09

    – 将博客部署到 Github Pages 上

    到目前为止,我们的本地博客就成功搭建了,但是现在我们只能通过本地连接查看博客,我们要做的是让其他人也能够访问我们的博客,这就需要我们将博客部署到Github Pages上

    一、注册 Github 账户:点击此处访问 Github 官网,点击 Sign Up 注册账户

    二、创建项目代码库:点击 New repository 开始创建,步骤及注意事项见图:
    10

    三、配置 SSH 密钥:只有配置好 SSH 密钥后,我们才可以通过 git 操作实现本地代码库与 Github 代码库同步,在你第一次新建的文件夹里面(如:我的文件夹为:E\TRHX_BlogGit Bash Here 输入以下命令:

    1
    2
    $ ssh-keygen -t rsa -C "your email@example.com"
    //引号里面填写你的邮箱地址,比如我的是tanrenhou@126.com

    之后会出现:

    1
    2
    3
    Generating public/private rsa key pair.
    Enter file in which to save the key (/c/Users/you/.ssh/id_rsa):
    //到这里可以直接回车将密钥按默认文件进行存储

    然后会出现:

    1
    2
    3
    Enter passphrase (empty for no passphrase):
    //这里是要你输入密码,其实不需要输什么密码,直接回车就行
    Enter same passphrase again:

    接下来屏幕会显示:

    1
    2
    3
    4
    5
    6
    Your identification has been saved in /c/Users/you/.ssh/id_rsa.
    Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.
    The key fingerprint is:
    这里是各种字母数字组成的字符串,结尾是你的邮箱
    The key's randomart image is:
    这里也是各种字母数字符号组成的字符串

    运行以下命令,将公钥的内容复制到系统粘贴板上

    1
    $ clip < ~/.ssh/id_rsa.pub

    四、在 GitHub 账户中添加你的公钥

    1.登陆 GitHub,进入 Settings
    11

    2.点击 SSH and GPG Keys
    12

    3.选择 New SSH key
    13

    4.粘贴密钥:
    14

    五、测试

    输入以下命令:注意:git@github.com不要做任何更改!

    1
    $ ssh -T git@github.com

    之后会显示:
    15

    输入 yes 后会显示:
    16
    此时表示设置正确

    六、配置 Git 个人信息

    Git 会根据用户的名字和邮箱来记录提交,GitHub 也是用这些信息来做权限的处理,输入以下命令进行个人信息的设置,把名称和邮箱替换成你自己的,名字可以不是 GitHub 的昵称,但为了方便记忆,建议与 GitHub 一致

    1
    2
    $ git config --global user.name "此处填你的用户名"
    $ git config --global user.email "此处填你的邮箱"

    到此为止 SSH Key 配置成功,本机已成功连接到 Github

    – 将本地的 Hexo 文件更新到 Github 的库中

    一、登录 Github 打开自己的项目 yourname.github.io
    17

    二、鼠标移到 Clone or download 按钮,选择 Use SSH
    18

    三、一键复制地址
    19

    四、打开你创建的 Hexo 文件夹(如:E:\TRHX_Blog\Hexo),右键用记事本(或者Notepad++、Vs Code等)打开该文件夹下的 _config.yml 文件
    20

    五、按下图修改 _config.yml 文件并保存
    21

    六、在 Hexo 文件夹下分别执行以下命令

    1
    2
    $ hexo g
    $ hexo d

    或者直接执行

    1
    $ hexo g -d

    执行完之后会让你输入你的 Github 的账号和密码,如果此时报以下错误,说明你的 deployer 没有安装成功

    1
    ERROR Deployer not found: git

    需要执行以下命令再安装一次:

    1
    npm install hexo-deployer-git --save

    再执行 hexo g -d,你的博客就会部署到 Github 上了

    七、访问博客

    你的博客地址:https://你的用户名.github.io,比如我的是:https://trhx.github.io ,现在每个人都可以通过此链接访问你的博客了

    – 如何在博客上发表文章

    博客已经成功搭建了,但是我们该怎么写博客呢?

    一、新建一个空文章,输入以下命令,会在项目 \Hexo\source\_posts 中生成 文章标题.md 文件,文章标题根据需要命名

    1
    $ hexo n "文章标题"

    也可以直接在 \Hexo\source\_posts 目录下右键鼠标新建文本文档,改后缀为 .md 即可,这种方法比较方便

    二、用编辑器编写文章

    md 全称 Markdown, Markdown 是 2004 年由 John Gruberis 设计和开发的纯文本格式的语法,非常的简单实用,常用的标记符号屈指可数,几分钟即可学会, .md 文件可以使用支持 Markdown 语法的编辑器编辑,然后将写好的文章(.md文件)保存到 \Hexo\source\_posts 文件夹下即可

    推荐 Windows 上使用 MarkdownPad2 或者 小书匠 编辑器,macOS 上使用 Mou 编辑器,Linux 上使用 Remarkable 编辑器,Web 端上使用 简书 ,另外可以参考我的另一篇文章:《主流 Markdown 编辑器推荐》
    当我们用编辑器写好文章后,可以使用以下命令将其推送到服务器上
    1
    2
    $ hexo g
    $ hexo d


    或者将两个命令合二为一输入以下命令:
    1
    $ hexo d -g


    现在访问你的博客就可以看见写好的文章啦!
    参考资料:《10款流行的Markdown编辑器》 (By xiaoxiao_engineer)
         《献给写作者的 Markdown 新手指南》 (By 简书)
         《认识与入门 Markdown》 (By Te_Lee)
         《markdown简明语法》 (By 不如)
         《markdown基本语法》 (By 高鸿祥)
         《Markdown 公式指导手册》 (By Harries)


    # – 如何为博客更换自己喜欢的主题

    博客也搭建好了,文章也会写了,但是!!!默认的主题并不喜欢怎么办?现在,我们就来为自己的博客更换自己喜欢的主题

    点击此处进入 Hexo 官网的主题专栏,我们可以看见有许多的主题供我们选择
    22

    我们要做的就是把主题克隆过来,在此我们以主题 Aero-Dual 为例,点进去我们就可以看见该主题作者的博客,鼠标滑到底,我们可以看见 Theme By Levblanc 的字样(其他主题类似),点击作者 Levblanc ,页面就会跳转到该主题所有的相关文件在 Github 上的地址,复制该地址
    23
    24
    25

    再打开 Hexo 文件夹下的 themes 目录(如:E:\TRHX_Blog\Hexo\themes),右键 Git Bash Here,输入以下命令:

    1
    $ git clone 此处填写你刚才复制的主题地址

    比如要安装 Aero-Dual 主题,则输入命令:

    1
    $ git clone https://github.com/levblanc/hexo-theme-aero-dual

    等待下载完成后即可在 themes 目录下生成 hexo-theme-aero-dual 文件夹,然后打开 Hexo 文件夹下的配置文件 _config.yml ,找到关键字 theme,修改参数为:theme:hexo-theme-aero-dual (其他主题修改成相应名称即可),再次注意冒号后面有一个空格!
    26

    返回 Hexo 目录,右键 Git Bash Here ,输入以下命令开始部署主题:

    1
    2
    $ hexo g   
    $ hexo s

    此时打开浏览器,访问 http://localhost:4000/ 就可看见我们的主题已经更换了,如果感觉效果满意,我们就可以把它部署到Github上了

    打开 Hexo 文件夹,右键 Git Bash Here ,输入以下命令:

    1
    2
    3
    $ hexo clean  
    //该命令的作用是清除缓存,若不输入此命令,服务器有可能更新不了主题
    $ hexo g -d

    此时访问自己的博客即可看见更换后的主题,但我们仍然需要对主题的相关配置进行修改,比如网站标题,图标等等,Hexo 中有两份主要的配置文件,名称都是 _config.yml ,它们均是用于站点配置使用的。其中,一份位于站点根目录下(比如我的:E:\TRHX_Blog\Hexo\_config.yml),主要包含 Hexo 本身整站的配置;另一份位于主题目录下(比如我的:E:\TRHX_Blog\Hexo\themes\hexo-theme-aero-dual\_config.yml),这份配置由主题作者提供,主要用于配置主题相关的选项,一般 _config.yml 文件里都有相关注释,按需修改即可

    参考资料:《有哪些好看的 Hexo 主题?》 (知乎)
         《Hexo | 配置》 (Hexo官方文档)
         《hexo常用命令笔记》 (By 小弟调调)

    – 为你的 Hexo 博客配置个性域名

    本人在配置域名的时候问题百出,百度的各种方法都不管用,打开网站总是 404,可能是我太笨了  o(╥﹏╥)o ,不过好在后来终于解决了这个问题

    首先我们要购买域名,阿里云腾讯云都可以,也不贵,一年几十块钱,最便宜几块钱也能买到,以阿里云为例,我购买的域名是 itrhx.com,购买过程就不赘述了,选择阿里云的解析平台,来到阿里云的管理控制台,点击进入域名解析列表或者直接点击域名后面的解析
    27

    方法一:点击添加记录,需要添加两个记录,两个记录类型都是 CNAME ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都是填你自己的博客地址(比如我的是:trhx.github.io),保存之后域名解析就完成了!
    28
    方法二:两个记录类型为 A ,第一个主机记录为 @ ,第二个主机记录为 www,记录值都为博客的 IP 地址,IP 地址可以 cmd 中输入 ping 你的博客地址 获得(比如我的:ping trhx.github.io),保存之后域名解析就完成了!

    有关解析记录类型的区别可以参考《域名解析中A记录、CNAME、MX记录、NS记录的区别和联系》

    为了使 GitHub 接收我们的域名,还需要在博客的根目录下添加一个名为 CNAME 的文件(注意不要加.txt,没有任何后缀名!),这个文件放到 Hexo 文件夹的 source 里面,(比如我的是:E:\TRHX_Blog\Hexo\source),文件里面填写你的域名(加不加www都行),比如要填写我的域名,文件里面就写:www.itrhx.com 或者 itrhx.com,经过以上操作,别人就可以通过 www.itrhx.comitrhx.comtrhx.github.io 三个当中任意一个访问我的博客了!你的也一样!

    有关加不加www的问题有以下区别:

    如果你填写的是没有www的,比如 itrhx.com,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 https://itrhx.com

    如果你填写的是带www的,比如 www.itrhx.com ,那么无论是访问 https://www.itrhx.com 还是 https://itrhx.com ,都会自动跳转到 http://www.itrhx.com

    30

    如果你在其他平台购买域名,或者选择 DNSPod 等其他域名解析,操作方法大同小异,遇到问题可自行百度解决!

    参考资料:《推荐几家域名注册服务商》 (By Jelly Bool)
         《盘点十大免费DNS域名解析服务:稳定、可靠》

    – 结语

    一顿操作下来虽然有点儿累,但看见拥有了自己的博客还是非常有成就感的,人生就是需要折腾,那么现在就开始你的创作之旅吧!文章的不断积累,你会从中受益很多的!另外,这是一篇小白写的适用于小白的博客搭建教程,比较详细,有这方面基础的可以百度有简略一点儿的教程,文中如有错误还请大佬指出改正!文中涉及参考资料如有侵权请联系我删除!

    ]]>
    - - - - - Hexo - - - - - - - Github Pages - - Hexo - - - -
    - - - - - Hello World! - - /2018/08/10/A01-hello-world/ - -

    人类的幸福和欢乐在于奋斗,而最有价值的是为理想而奋斗! ——— 苏格拉底

    Human happiness and joy lie in struggle, and what is most valuable is striving for ideals! ——— Socrates


    ]]>
    - - - - - BLOG - - - - - - - BLOG - - - -
    - - - - + + + <![CDATA[用 VPS 搭建一个自己的 SSR 服务器]]> + %2F2020%2F01%2F10%2FA61-build-a-SSR-server-with-VPS%2F + + + VPS + + + VPS + SSR + + + + <![CDATA[2019年总结【跨越今天,更不平凡】]]> + %2F2019%2F12%2F31%2FA60-2019-summary%2F + + + BLOG + + + 年终总结 + + + + <![CDATA[Python3 爬虫实战 — 瓜子全国二手车]]> + %2F2019%2F11%2F15%2FA59-pyspider-guazi%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 瓜子二手车 + + + + <![CDATA[Python3 爬虫实战 — 58同城武汉出租房【加密字体对抗】]]> + %2F2019%2F10%2F21%2FA58-pyspider-58tongcheng%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 58同城 + + + + <![CDATA[Python3 爬虫实战 — 模拟登陆12306【点触验证码对抗】]]> + %2F2019%2F10%2F21%2FA57-pyspider-12306-login%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 12306 + + + + <![CDATA[Python3 爬虫实战 — 模拟登陆哔哩哔哩【滑动验证码对抗】]]> + %2F2019%2F10%2F21%2FA56-pyspider-bilibili-login%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 哔哩哔哩 + + + + <![CDATA[Python3 爬虫实战 — 虎扑论坛步行街]]> + %2F2019%2F10%2F12%2FA55-pyspider-hupu%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 虎扑论坛 + + + + <![CDATA[Python3 爬虫实战 — 安居客武汉二手房]]> + %2F2019%2F10%2F09%2FA54-pyspider-anjuke%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 安居客 + + + + <![CDATA[使用 Hexo-Git-Backup 插件备份你的 Hexo 博客]]> + %2F2019%2F09%2F29%2FA53-hexo-backup%2F + + + Hexo + + + Hexo + 备份 + + + + <![CDATA[Python3 爬虫实战 — 豆瓣电影TOP250]]> + %2F2019%2F09%2F28%2FA52-pyspider-doubantop250%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 豆瓣电影 + + + + <![CDATA[Python3 爬虫实战 — 猫眼电影TOP100]]> + %2F2019%2F09%2F24%2FA51-pyspider-maoyantop100%2F + + + Python3 学习笔记 + 爬虫实战 + + + 爬虫 + 猫眼电影 + + + + <![CDATA[Python3 爬虫学习笔记 C18]]> + %2F2019%2F09%2F21%2FA50-Python3-spider-C18%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + pyspider + + + + <![CDATA[Python3 爬虫学习笔记 C17]]> + %2F2019%2F09%2F18%2FA49-Python3-spider-C17%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + pyspider + + + + <![CDATA[Hexo 博客提交百度、谷歌搜索引擎收录]]> + %2F2019%2F09%2F17%2FA48-submit-search-engine-inclusion%2F + + + Hexo + + + Hexo + SEO + + + + <![CDATA[Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS]]> + %2F2019%2F09%2F16%2FA47-hexo-deployed-to-github-and-coding%2F + + + Hexo + + + Hexo + Coding Pages + GitHub Pages + + + + <![CDATA[Python3 爬虫学习笔记 C16]]> + %2F2019%2F09%2F14%2FA46-Python3-spider-C16%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + Redis + + + + <![CDATA[Python3 爬虫学习笔记 C15]]> + %2F2019%2F09%2F10%2FA45-Python3-spider-C15%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 代理 + + + + <![CDATA[Python3 爬虫学习笔记 C14]]> + %2F2019%2F09%2F08%2FA44-Python3-spider-C14%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 点触验证码 + + + + <![CDATA[Python3 爬虫学习笔记 C13]]> + %2F2019%2F09%2F07%2FA43-Python3-spider-C13%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 滑动验证码 + + + + <![CDATA[Python3 爬虫学习笔记 C12]]> + %2F2019%2F09%2F05%2FA42-Python3-spider-C12%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 图形验证码 + + + + <![CDATA[Python3 爬虫学习笔记 C11]]> + %2F2019%2F09%2F04%2FA41-Python3-spider-C11%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + MongoDB + + + + <![CDATA[Python3 爬虫学习笔记 C10]]> + %2F2019%2F09%2F03%2FA40-Python3-spider-C10%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + MySQL + + + + <![CDATA[Python3 爬虫学习笔记 C09]]> + %2F2019%2F08%2F27%2FA39-Python3-spider-C09%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 文件储存 + + + + <![CDATA[Python3 爬虫学习笔记 C08]]> + %2F2019%2F08%2F26%2FA38-Python3-spider-C08%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + Beautiful Soup + + + + <![CDATA[Python3 爬虫学习笔记 C07]]> + %2F2019%2F08%2F25%2FA37-Python3-spider-C07%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + lxml + XPath + + + + <![CDATA[Python3 爬虫学习笔记 C06]]> + %2F2019%2F08%2F24%2FA36-Python3-spider-C06%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + 正则表达式 + + + + <![CDATA[Python3 爬虫学习笔记 C05]]> + %2F2019%2F08%2F23%2FA35-Python3-spider-C05%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + Selenium + 无界面浏览器 + + + + <![CDATA[常见 User-Agent 大全]]> + %2F2019%2F08%2F23%2FA34-UserAgent%2F + + + Python3 学习笔记 + 学习经验 + + + 爬虫 + User-Agent + + + + <![CDATA[Selenium 显式等待条件及其含义]]> + %2F2019%2F08%2F23%2FA33-selenium%2F + + + Python3 学习笔记 + 学习经验 + + + 爬虫 + Selenium + + + + <![CDATA[Python3 爬虫学习笔记 C04]]> + %2F2019%2F08%2F23%2FA32-Python3-spider-C04%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + Selenium + + + + <![CDATA[Python3 爬虫学习笔记 C03]]> + %2F2019%2F08%2F23%2FA31-Python3-spider-C03%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + Ajax + + + + <![CDATA[Python3 爬虫学习笔记 C02]]> + %2F2019%2F08%2F23%2FA30-Python3-spider-C02%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + requests + + + + <![CDATA[Python3 爬虫学习笔记 C01]]> + %2F2019%2F08%2F23%2FA29-Python3-spider-C01%2F + + + Python3 学习笔记 + 爬虫学习 + + + 爬虫 + urllib + + + + <![CDATA[一个 JS 脚本实现网站预加载,提升页面加载速度]]> + %2F2019%2F08%2F23%2FA24-instant.page%2F + + + WEB前端 + + + instant.page + JS 预加载 + + + + <![CDATA[网站ICP备案和公安备案流程]]> + %2F2019%2F08%2F23%2FA23-beian%2F + + + Hexo + WEB前端 + + + ICP备案 + 公安备案 + + + + <![CDATA[恶意刷留言者——你是什么垃圾?]]> + %2F2019%2F08%2F23%2FA25-SB%2F + + + BLOG + + + 垃圾 + + + + <![CDATA[利用官方支持为基于GitHub Pages的Hexo博客启用HTTPS]]> + %2F2019%2F08%2F11%2FA28-hexo-add-https%2F + + + Hexo + + + Hexo + HTTPS + + + + <![CDATA[Github+jsDelivr+PicGo 打造稳定快速、高效免费图床]]> + %2F2019%2F08%2F01%2FA27-image-hosting%2F + + + 图床 + + + jsDelivr + 图床 + PicGo + + + + <![CDATA[利用Cloudflare为基于GitHub Pages的Hexo博客添加HTTPS支持]]> + %2F2019%2F07%2F31%2FA26-hexo-add-https%2F + + + Hexo + + + Hexo + HTTPS + + + + <![CDATA[Eclipse 通过 JDBC 连接 SQL Server]]> + %2F2019%2F05%2F14%2FA22-eclipse-connects-to-sql%2F + + + Java + + + JDBC + SQL Server 2012 + Elicpse + + + + <![CDATA[Python PEP8 代码规范常见问题及解决方法]]> + %2F2019%2F04%2F15%2FA21-PEP8%2F + + + Python3 学习笔记 + 学习经验 + + + Python + PEP8 + + + + <![CDATA[VMware Pro 15 安装 Deepin15.9 国产操作系统]]> + %2F2019%2F04%2F14%2FA20-install-deepin15.9%2F + + + Linux + + + VMware + Deepin + + + + <![CDATA[Windows 系统中 Pygame 的安装]]> + %2F2019%2F03%2F10%2FA19-install-pygame%2F + + + Python3 学习笔记 + 学习经验 + + + Pygame + Python + + + + <![CDATA[免费CDN:jsDelivr + Github]]> + %2F2019%2F02%2F10%2FA18-free-cdn%2F + + + CDN + + + jsDelivr + CDN + + + + <![CDATA[新年快乐!]]> + %2F2019%2F02%2F05%2FA17-happy-new-year%2F + + + BLOG + + + BLOG + + + + <![CDATA[一台电脑使用两个/多个GitHub账号部署两个/多个Hexo博客]]> + %2F2019%2F01%2F18%2FA16-deploy-two-or-more-hexo-blogs%2F + + + Hexo + + + Hexo + Github + + + + <![CDATA[Python3 基础学习笔记 C09]]> + %2F2018%2F11%2F16%2FA15-Python3-basic-C09%2F + + + Python3 学习笔记 + 基础学习 + + + 文件 + 异常 + + + + <![CDATA[Python3 基础学习笔记 C08]]> + %2F2018%2F11%2F11%2FA14-Python3-basic-C08%2F + + + Python3 学习笔记 + 基础学习 + + + + 继承 + + + + <![CDATA[Python3 基础学习笔记 C07]]> + %2F2018%2F11%2F03%2FA13-Python3-basic-C07%2F + + + Python3 学习笔记 + 基础学习 + + + 函数 + 模块 + + + + <![CDATA[Python3 基础学习笔记 C06]]> + %2F2018%2F10%2F30%2FA12-Python3-basic-C06%2F + + + Python3 学习笔记 + 基础学习 + + + input()函数 + while循环 + + + + <![CDATA[Python3 基础学习笔记 C05]]> + %2F2018%2F10%2F27%2FA11-Python3-basic-C05%2F + + + Python3 学习笔记 + 基础学习 + + + 字典 + + + + <![CDATA[Python3 基础学习笔记 C04]]> + %2F2018%2F10%2F24%2FA10-Python3-basic-C04%2F + + + Python3 学习笔记 + 基础学习 + + + if语句 + + + + <![CDATA[Python3 基础学习笔记 C03]]> + %2F2018%2F10%2F11%2FA09-Python3-basic-C03%2F + + + Python3 学习笔记 + 基础学习 + + + 操作列表 + + + + <![CDATA[Python3 基础学习笔记 C02]]> + %2F2018%2F09%2F16%2FA08-Python3-basic-C02%2F + + + Python3 学习笔记 + 基础学习 + + + 列表 + + + + <![CDATA[Python3 基础学习笔记 C01]]> + %2F2018%2F09%2F13%2FA07-Python3-basic-C01%2F + + + Python3 学习笔记 + 基础学习 + + + 数据类型 + 变量 + + + + <![CDATA[VMware Pro 14 安装 Ubuntu 18.04 详细教程]]> + %2F2018%2F09%2F09%2FA06-install-ubuntu18.04%2F + + + Linux + + + VMware + Ubuntu + + + + <![CDATA[主流 Markdown 编辑器推荐]]> + %2F2018%2F08%2F29%2FA05-markdown-editor%2F + + + Markdown + + + Markdown + 编辑器 + + + + <![CDATA[Hexo 博客主题个性化]]> + %2F2018%2F08%2F27%2FA04-Hexo-blog-topic-personalization%2F + + + Hexo + + + Hexo + 主题个性化 + Material X + spfk + + + + <![CDATA[Markdown 语法&技巧总结]]> + %2F2018%2F08%2F25%2FA03-markdown%2F + + + Markdown + + + Markdown + 技巧 + + + + <![CDATA[使用 Github Pages 和 Hexo 搭建自己的独立博客]]> + %2F2018%2F08%2F15%2FA02-hexo-blog%2F + + + Hexo + + + Github Pages + Hexo + + + + <![CDATA[Hello World!]]> + %2F2018%2F08%2F10%2FA01-hello-world%2F + + + BLOG + + + BLOG + + diff --git a/sitemap.xml b/sitemap.xml index b816b0fbb..58bc9865e 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -1,115 +1,178 @@ + + https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/ + + 2020-03-14T08:00:06.127Z + + + https://www.itrhx.com/friends/index.html - 2020-03-04T14:05:45.065Z + 2020-03-14T07:55:33.386Z - https://www.itrhx.com/2020/01/10/A61-build-a-SSR-server-with-VPS/ + https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/ - 2020-03-02T08:02:23.404Z + 2020-03-14T06:21:36.668Z - https://www.itrhx.com/about/index.html + https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/ - 2020-03-02T05:42:14.094Z + 2020-03-14T06:19:49.153Z - https://www.itrhx.com/2019-nCoV/index.html + https://www.itrhx.com/2019/08/01/A27-image-hosting/ - 2020-02-28T07:25:47.215Z + 2020-03-14T06:17:16.724Z - https://www.itrhx.com/box/index.html + https://www.itrhx.com/2019/07/31/A26-hexo-add-https/ - 2020-02-27T12:02:54.700Z + 2020-03-14T06:13:57.930Z - https://www.itrhx.com/comments/index.html + https://www.itrhx.com/2019/08/23/A25-SB/ - 2020-02-27T11:40:10.042Z + 2020-03-14T06:11:57.038Z - https://www.itrhx.com/2019/12/31/A60-2019-summary/ + https://www.itrhx.com/2019/08/23/A23-beian/ - 2020-02-07T07:34:41.017Z + 2020-03-14T06:07:46.135Z - https://www.itrhx.com/2019/01/18/A16-deploy-two-or-more-hexo-blogs/ + https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/ - 2020-02-07T07:30:20.946Z + 2020-03-14T05:59:46.063Z - https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ + https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/ - 2019-12-31T15:41:01.525Z + 2020-03-14T05:53:55.551Z - https://www.itrhx.com/2019/08/11/A28-hexo-add-https/ + https://www.itrhx.com/2019/02/10/A18-free-cdn/ - 2019-12-31T15:24:54.792Z + 2020-03-14T05:51:31.881Z - https://www.itrhx.com/2019/08/01/A27-image-hosting/ + https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/ - 2019-12-31T15:24:47.016Z + 2020-03-14T05:48:49.164Z - https://www.itrhx.com/2019/08/23/A23-beian/ + https://www.itrhx.com/2018/08/29/A05-markdown-editor/ - 2019-12-31T11:24:22.073Z + 2020-03-14T05:44:24.275Z - https://www.itrhx.com/2018/09/13/A07-Python3-basic-C01/ + https://www.itrhx.com/2018/08/27/A04-Hexo-blog-topic-personalization/ - 2019-12-29T07:27:42.544Z + 2020-03-14T05:37:41.803Z + + + + + https://www.itrhx.com/2018/08/25/A03-markdown/ + + 2020-03-14T05:26:44.635Z https://www.itrhx.com/2018/08/15/A02-hexo-blog/ - 2019-12-29T07:21:04.675Z + 2020-03-14T05:24:47.844Z - https://www.itrhx.com/2019/07/31/A26-hexo-add-https/ + https://www.itrhx.com/2020/01/10/A61-build-a-SSR-server-with-VPS/ - 2019-12-29T07:20:24.370Z + 2020-03-02T08:02:23.404Z - https://www.itrhx.com/2019/09/16/A47-hexo-deployed-to-github-and-coding/ + https://www.itrhx.com/about/index.html + + 2020-03-02T05:42:14.094Z + + + + + https://www.itrhx.com/2019-nCoV/index.html + + 2020-02-28T07:25:47.215Z + + + + + https://www.itrhx.com/box/index.html + + 2020-02-27T12:02:54.700Z + + + + + https://www.itrhx.com/comments/index.html + + 2020-02-27T11:40:10.042Z + + + + + https://www.itrhx.com/2019/12/31/A60-2019-summary/ + + 2020-02-07T07:34:41.017Z + + + + + https://www.itrhx.com/2019/01/18/A16-deploy-two-or-more-hexo-blogs/ - 2019-12-29T07:20:11.697Z + 2020-02-07T07:30:20.946Z + + + + + https://www.itrhx.com/2019/08/11/A28-hexo-add-https/ + + 2019-12-31T15:24:54.792Z + + + + + https://www.itrhx.com/2018/09/13/A07-Python3-basic-C01/ + + 2019-12-29T07:27:42.544Z @@ -135,14 +198,14 @@ - https://www.itrhx.com/games/gobang/index.html + https://www.itrhx.com/games/piano/index.html 2019-12-29T06:55:50.754Z - https://www.itrhx.com/games/piano/index.html + https://www.itrhx.com/games/gobang/index.html 2019-12-29T06:55:50.754Z @@ -170,14 +233,14 @@ - https://www.itrhx.com/games/cat/index.html + https://www.itrhx.com/games/element/index.html 2019-12-29T06:55:50.751Z - https://www.itrhx.com/games/element/index.html + https://www.itrhx.com/games/cat/index.html 2019-12-29T06:55:50.751Z @@ -393,13 +456,6 @@ - - https://www.itrhx.com/2019/09/03/A40-Python3-spider-C10/ - - 2019-09-24T12:40:41.602Z - - - https://www.itrhx.com/2019/08/26/A38-Python3-spider-C08/ @@ -428,13 +484,6 @@ - - https://www.itrhx.com/2019/08/23/A30-Python3-spider-C02/ - - 2019-09-24T12:40:04.967Z - - - https://www.itrhx.com/2019/08/23/A29-Python3-spider-C01/ @@ -463,41 +512,6 @@ - - https://www.itrhx.com/2019/08/23/A25-SB/ - - 2019-09-09T14:09:57.901Z - - - - - https://www.itrhx.com/2019/02/10/A18-free-cdn/ - - 2019-09-09T14:09:28.003Z - - - - - https://www.itrhx.com/2019/05/14/A22-eclipse-connects-to-sql/ - - 2019-09-09T14:02:06.957Z - - - - - https://www.itrhx.com/2018/09/09/A06-install-ubuntu18.04/ - - 2019-09-09T14:00:24.009Z - - - - - https://www.itrhx.com/2019/04/14/A20-install-deepin15.9/ - - 2019-09-09T14:00:05.684Z - - - https://www.itrhx.com/2019/08/23/A24-instant.page/ @@ -505,20 +519,6 @@ - - https://www.itrhx.com/2018/08/29/A05-markdown-editor/ - - 2019-09-09T13:40:31.217Z - - - - - https://www.itrhx.com/2018/08/25/A03-markdown/ - - 2019-09-09T13:40:22.061Z - - - https://www.itrhx.com/tags/index.html diff --git a/tags/Coding-Pages/index.html b/tags/Coding-Pages/index.html index 344f9bc8f..843992834 100644 --- a/tags/Coding-Pages/index.html +++ b/tags/Coding-Pages/index.html @@ -439,14 +439,14 @@
    - +

    - Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HPPTS + Hexo 双线部署到 Coding Pages 和 GitHub Pages 并实现全站 HTTPS

    diff --git a/tags/Github-Pages/index.html b/tags/Github-Pages/index.html index e9b715eec..cacec5218 100644 --- a/tags/Github-Pages/index.html +++ b/tags/Github-Pages/index.html @@ -439,14 +439,14 @@
    - +