13.5 Downloader Middleware 的用法
Downloader Middleware 即下载中间件,它是处于 Scrapy 的 Request 和 Response 之间的处理模块。我们首先来看看它的架构,如图 13-1 所示。 Scheduler 从队列中拿出一个 Request 发送给 Downloader 执行下载,这个过程会经过 Downloader Middleware 的处理。另外,当 Downloader 将 Request 下载完成得到 Response 返回给 Spider 时会再次经过 Downloader Middleware 处理。 也就是说,Downloader Middleware 在整个架构中起作用的位置是以下两个。
- 在 Scheduler 调度出队列的 Request 发送给 Downloader 下载之前,也就是我们可以在 Request 执行下载之前对其进行修改。
- 在下载后生成的 Response 发送给 Spider 之前,也就是我们可以在生成 Resposne 被 Spider 解析之前对其进行修改。
Downloader Middleware 的功能十分强大,修改 User-Agent、处理重定向、设置代理、失败重试、设置 Cookies 等功能都需要借助它来实现。下面我们来了解一下 Downloader Middleware 的详细用法。
1. 使用说明
需要说明的是,Scrapy 其实已经提供了许多 Downloader Middleware,比如负责失败重试、自动重定向等功能的 Middleware,它们被 DOWNLOADER_MIDDLEWARES_BASE 变量所定义。 DOWNLOADER_MIDDLEWARES_BASE 变量的内容如下所示:
1 |
{ |
这是一个字典格式,字典的键名是 Scrapy 内置的 Downloader Middleware 的名称,键值代表了调用的优先级,优先级是一个数字,数字越小代表越靠近 Scrapy 引擎,数字越大代表越靠近 Downloader。每个 Downloader Middleware 都可以定义 process_request() 和 request_response() 方法来分别处理请求和响应,对于 process_request() 方法来说,优先级数字越小越先被调用,对于 process_response() 方法来说,优先级数字越大越先被调用。。 如果自己定义的 Downloader Middleware 要添加到项目里,DOWNLOADER_MIDDLEWARES_BASE 变量不能直接修改。Scrapy 提供了另外一个设置变量 DOWNLOADER_MIDDLEWARES,我们直接修改这个变量就可以添加自己定义的 Downloader Middleware,以及禁用 DOWNLOADER_MIDDLEWARES_BASE 里面定义的 Downloader Middleware。下面我们具体来看看 Downloader Middleware 的使用方法。
2. 核心方法
Scrapy 内置的 Downloader Middleware 为 Scrapy 提供了基础的功能,但在项目实战中我们往往需要单独定义 Downloader Middleware。不用担心,这个过程非常简单,我们只需要实现某几个方法即可。 每个 Downloader Middleware 都定义了一个或多个方法的类,核心的方法有如下三个。
- process_request(request, spider)
- process_response(request, response, spider)
- process_exception(request, exception, spider)
我们只需要实现至少一个方法,就可以定义一个 Downloader Middleware。下面我们来看看这三个方法的详细用法。
process_request(request, spider)
Request 被 Scrapy 引擎调度给 Downloader 之前,process_request() 方法就会被调用,也就是在 Request 从队列里调度出来到 Downloader 下载执行之前,我们都可以用 process_request() 方法对 Request 进行处理。方法的返回值必须为 None、Response 对象、Request 对象之一,或者抛出 IgnoreRequest 异常。 process_request() 方法的参数有如下两个。
- request,即 Request 对象,即被处理的 Request
- spider,即 Spdier 对象,即此 Request 对应的 Spider
返回类型不同,产生的效果也不同。下面归纳一下不同的返回情况。
- 当返回是 None 时,Scrapy 将继续处理该 Request,接着执行其他 Downloader Middleware 的 process_request() 方法,一直到 Downloader 把 Request 执行后得到 Response 才结束。这个过程其实就是修改 Request 的过程,不同的 Downloader Middleware 按照设置的优先级顺序依次对 Request 进行修改,最后送至 Downloader 执行。
- 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_request() 和 process_exception() 方法就不会被继续调用,每个 Downloader Middleware 的 process_response() 方法转而被依次调用。调用完毕之后,直接将 Response 对象发送给 Spider 来处理。
- 当返回为 Request 对象时,更低优先级的 Downloader Middleware 的 process_request() 方法会停止执行。这个 Request 会重新放到调度队列里,其实它就是一个全新的 Request,等待被调度。如果被 Scheduler 调度了,那么所有的 Downloader Middleware 的 process_request() 方法会被重新按照顺序执行。
- 如果 IgnoreRequest 异常抛出,则所有的 Downloader Middleware 的 process_exception() 方法会依次执行。如果没有一个方法处理这个异常,那么 Request 的 errorback() 方法就会回调。如果该异常还没有被处理,那么它便会被忽略。
process_response(request, response, spider)
Downloader 执行 Request 下载之后,会得到对应的 Response。Scrapy 引擎便会将 Response 发送给 Spider 进行解析。在发送之前,我们都可以用 process_response() 方法来对 Response 进行处理。方法的返回值必须为 Request 对象、Response 对象之一,或者抛出 IgnoreRequest 异常。 process_response() 方法的参数有如下三个。
- request,是 Request 对象,即此 Response 对应的 Request。
- response,是 Response 对象,即此被处理的 Response。
- spider,是 Spider 对象,即此 Response 对应的 Spider。
下面对不同的返回情况做一下归纳:
- 当返回为 Request 对象时,更低优先级的 Downloader Middleware 的 process_response() 方法不会继续调用。该 Request 对象会重新放到调度队列里等待被调度,它相当于一个全新的 Request。然后,该 Request 会被 process_request() 方法顺次处理。
- 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_response() 方法会继续调用,继续对该 Response 对象进行处理。
- 如果 IgnoreRequest 异常抛出,则 Request 的 errorback() 方法会回调。如果该异常还没有被处理,那么它便会被忽略。
process_exception(request, exception, spider)
当 Downloader 或 process_request() 方法抛出异常时,例如抛出 IgnoreRequest 异常,process_exception() 方法就会被调用。方法的返回值必须为 None、Response 对象、Request 对象之一。 process_exception() 方法的参数有如下三个。
- request,即 Request 对象,即产生异常的 Request
- exception,即 Exception 对象,即抛出的异常
- spdier,即 Spider 对象,即 Request 对应的 Spider
下面归纳一下不同的返回值。
- 当返回为 None 时,更低优先级的 Downloader Middleware 的 process_exception() 会被继续顺次调用,直到所有的方法都被调度完毕。
- 当返回为 Response 对象时,更低优先级的 Downloader Middleware 的 process_exception() 方法不再被继续调用,每个 Downloader Middleware 的 process_response() 方法转而被依次调用。
- 当返回为 Request 对象时,更低优先级的 Downloader Middleware 的 process_exception() 也不再被继续调用,该 Request 对象会重新放到调度队列里面等待被调度,它相当于一个全新的 Request。然后,该 Request 又会被 process_request() 方法顺次处理。
以上内容便是这三个方法的详细使用逻辑。在使用它们之前,请先对这三个方法的返回值的处理情况有一个清晰的认识。在自定义 Downloader Middleware 的时候,也一定要注意每个方法的返回类型。 下面我们用一个案例实战来加深一下对 Downloader Middleware 用法的理解。
3. 项目实战
新建一个项目,命令如下所示:
1 |
scrapy startproject scrapydownloadertest |
新建了一个 Scrapy 项目,名为 scrapydownloadertest。进入项目,新建一个 Spider,命令如下所示:
1 |
scrapy genspider httpbin httpbin |
新建了一个 Spider,名为 httpbin,源代码如下所示:
1 |
import scrapy |
接下来运行此 Spider,执行如下命令:
1 |
scrapy crawl httpbin |
Scrapy 运行结果包含 Scrapy 发送的 Request 信息,内容如下所示:
1 |
{"args": {}, |
我们观察一下 Headers,Scrapy 发送的 Request 使用的 User-Agent 是 Scrapy/1.4.0(+http://scrapy.org),这其实是由,这其实是由) Scrapy 内置的 UserAgentMiddleware 设置的,UserAgentMiddleware 的源码如下所示:
1 |
from scrapy import signals |
在 from_crawler() 方法中,首先尝试获取 settings 里面 USER_AGENT,然后把 USER_AGENT 传递给init() 方法进行初始化,其参数就是 user_agent。如果没有传递 USER_AGENT 参数就默认设置为 Scrapy 字符串。我们新建的项目没有设置 USER_AGENT,所以这里的 user_agent 变量就是 Scrapy。接下来,在 process_request() 方法中,将 user-agent 变量设置为 headers 变量的一个属性,这样就成功设置了 User-Agent。因此,User-Agent 就是通过此 Downloader Middleware 的 process_request() 方法设置的。 修改请求时的 User-Agent 可以有两种方式:一是修改 settings 里面的 USER_AGENT 变量;二是通过 Downloader Middleware 的 process_request() 方法来修改。 第一种方法非常简单,我们只需要在 setting.py 里面加一行 USER_AGENT 的定义即可:
1 |
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36' |
一般推荐使用此方法来设置。但是如果想设置得更灵活,比如设置随机的 User-Agent,那就需要借助 Downloader Middleware 了。所以接下来我们用 Downloader Middleware 实现一个随机 User-Agent 的设置。 在 middlewares.py 里面添加一个 RandomUserAgentMiddleware 的类,如下所示:
1 |
import random |
我们首先在类的 init() 方法中定义了三个不同的 User-Agent,并用一个列表来表示。接下来实现了 process_request() 方法,它有一个参数 request,我们直接修改 request 的属性即可。在这里我们直接设置了 request 对象的 headers 属性的 User-Agent,设置内容是随机选择的 User-Agent,这样一个 Downloader Middleware 就写好了。 不过,要使之生效我们还需要再去调用这个 Downloader Middleware。在 settings.py 中,将 DOWNLOADER_MIDDLEWARES 取消注释,并设置成如下内容:
1 |
DOWNLOADER_MIDDLEWARES = {'scrapydownloadertest.middlewares.RandomUserAgentMiddleware': 543,} |
接下来我们重新运行 Spider,就可以看到 User-Agent 被成功修改为列表中所定义的随机的一个 User-Agent 了:
1 |
{"args": {}, |
我们就通过实现 Downloader Middleware 并利用 process_request() 方法成功设置了随机的 User-Agent。 另外,Downloader Middleware 还有 process_response() 方法。Downloader 对 Request 执行下载之后会得到 Response,随后 Scrapy 引擎会将 Response 发送回 Spider 进行处理。但是在 Response 被发送给 Spider 之前,我们同样可以使用 process_response() 方法对 Response 进行处理。比如这里修改一下 Response 的状态码,在 RandomUserAgentMiddleware 添加如下代码:
1 |
def process_response(self, request, response, spider): |
我们将 response 对象的 status 属性修改为 201,随后将 response 返回,这个被修改后的 Response 就会被发送到 Spider。 我们再在 Spider 里面输出修改后的状态码,在 parse() 方法中添加如下的输出语句:
1 |
self.logger.debug('Status Code: ' + str(response.status)) |
重新运行之后,控制台输出了如下内容:
1 |
[httpbin] DEBUG: Status Code: 201 |
可以发现,Response 的状态码成功修改了。 因此要想对 Response 进行后处理,就可以借助于 process_response() 方法。 另外还有一个 process_exception() 方法,它是用来处理异常的方法。如果需要异常处理的话,我们可以调用此方法。不过这个方法的使用频率相对低一些,在此不用实例演示。
4. 本节代码
本节源代码为:https://github.com/Python3WebSpider/ScrapyDownloaderTest。
5. 结语
本节讲解了 Downloader Middleware 的基本用法。此组件非常重要,是做异常处理和应对反爬处理的核心。后面我们会在实战中应用此组件来处理代理、Cookies 等内容。




图 13-1 Scrapy 架构 它可以分为如下的几个部分。
图 9-10 预装操作系统 推荐安装 CentOS 7 系统。 然后找到远程管理面板 远程连接的用户名和密码,也就是 SSH 远程连接服务器的信息。比如我使用的 IP 和端口是 153.36.65.214:20063,用户名是 root。命令行下输入如下内容:
图 9-11 配置页面 提示成功之后就可以进行拨号了。注意,在拨号之前测试 ping 任何网站都是不通的,因为当前网络还没联通。输入如下拨号命令:
图 9-12 拨号建立连接 断线重播的命令就是二者组合起来,先执行 adsl-stop,再执行 adsl-start。每次拨号,ifconfig 命令观察主机的 IP,发现主机的 IP 一直在变化,网卡名称叫作 ppp0,如图 9-13 所示。
图 9-13 网络设备信息 接下来,我们要做两件事:一是怎样将主机设置为代理服务器,二是怎样实时获取拨号主机的 IP。
图 9-14 运行结果 如果有正常的结果输出,并且 origin 的值为代理 IP 的地址,就证明 TinyProxy 配置成功了。
图 9-15 Hash 结构 如果有多台主机,只需要向 Hash 中添加映射即可。 另外,get() 方法就是从散列表中取出某台主机对应的代理。remove() 方法则是从散列表中移除对应的主机的代理。还有 names()、proxies()、all() 方法则是分别获取散列表中的主机列表、代理列表及所有主机代理映射。count() 方法则是返回当前散列表的大小,也就是可用代理的数目。 最后还有一个比较重要的方法 random(),它随机从散列表中取出一个可用代理,类似前面代理池的思想,确保每个代理都能被取到。 如果要对数据库进行操作,只需要初始化 RedisClient 对象,然后调用它的 set() 或者 remove() 方法,即可对散列表进行设置和删除。
图 9-16 示例输出 首先移除了代理,再进行拨号,拨号完成之后获取新的 IP,代理检测成功之后就设置到 Redis 散列表中,然后等待一段时间再重新进行拨号。 我们添加了多台拨号主机,这样就有多个稳定的定时更新的代理可用了。Redis 散列表会实时更新各台拨号主机的代理,如图 9-17 所示。
图 9-17 Hash 结构 图中所示是四台 ADSL 拨号主机配置并运行后的散列表的内容,表中的代理都是可用的。
图 9-18 主页面 访问 proxies 接口可以获得所有代理列表,如图 9-19 所示。
图 9-19 代理列表 访问 random 接口可以获取随机可用代理,如图 9-20 所示。
图 9-20 随机代理 我们只需将接口部署到服务器上,即可通过 Web 接口获取可用代理,获取方式和代理池类似。
图 13-5 Scrapy Shell 我们可以在命令行模式下输入命令调用对象的一些操作方法,回车之后实时显示结果。这与 Python 的命令行交互模式是类似的。 接下来,演示的实例都将页面的源码作为分析目标,页面源码如下所示:
图 13-2 页面结构 提取的方式可以是 CSS 选择器或 XPath 选择器。在这里我们使用 CSS 选择器进行选择,parse() 方法的改写如下所示:
图 13-3 页面底部 有一个 Next 按钮,查看一下源代码,可以发现它的链接是 /page/2/,实际上全链接就是:
图 13-4 爬取结果 长的 text 已经被处理并追加了省略号,短的 text 保持不变,author 和 tags 也都相应保存。
图 12-26 运行页面 也可以单独运行 pyspider 的某一个组件。 运行 Scheduler 的命令如下所示:
图 12-27 抓取进度 progress 中的 5m、1h、1d 指的是最近 5 分、1 小时、1 天内的请求情况,all 代表所有的请求情况。 蓝色的请求代表等待被执行的任务,绿色的代表成功的任务,黄色的代表请求失败后等待重试的任务,红色的代表失败次数过多而被忽略的任务,从这里我们可以直观看到爬取的进度和请求情况。
图 12-2 运行结果 这样可以启动 pyspider 的所有组件,包括 PhantomJS、ResultWorker、Processer、Fetcher、Scheduler、WebUI,这些都是 pyspider 运行必备的组件。最后一行输出提示 WebUI 运行在 5000 端口上。可以打开浏览器,输入链接
图 12-3 WebUI 页面 此页面便是 pyspider 的 WebUI,我们可以用它来管理项目、编写代码、在线调试、监控任务等。
图 12-4 创建项目 接下来会看到 pyspider 的项目编辑和调试页面,如图 12-5 所示。
图 12-5 调试页面 左侧就是代码的调试页面,点击左侧右上角的 run 单步调试爬虫程序,在左侧下半部分可以预览当前的爬取页面。右侧是代码编辑页面,我们可以直接编辑代码和保存代码,不需要借助于 IDE。 注意右侧,pyspider 已经帮我们生成了一段代码,代码如下所示:
图 12-6 操作示例 左栏左上角会出现当前 run 的配置文件,这里有一个 callback 为 on_start,这说明点击 run 之后实际是执行了 on_start() 方法。在 on_start() 方法中,我们利用 crawl() 方法生成一个爬取请求,那下方 follows 部分的数字 1 就代表了这一个爬取请求。 点击下方的 follows 按钮,即可看到生成的爬取请求的链接。每个链接的右侧还有一个箭头按钮,如图 12-7 所示。
图 12-7 操作示例 点击该箭头,我们就可以对此链接进行爬取,也就是爬取攻略的首页内容,如图 12-8 所示。
图 12-8 爬取结果 上方的 callback 已经变成了 index_page,这就代表当前运行了 index_page() 方法。index_page() 接收到的 response 参数就是刚才生成的第一个爬取请求的 Response 对象。index_page() 方法通过调用 doc() 方法,传入提取所有 a 节点的 CSS 选择器,然后获取 a 节点的属性 href,这样实际上就是获取了第一个爬取页面中的所有链接。然后在 index_page() 方法里遍历了所有链接,同时调用 crawl() 方法,就把这一个个的链接构造成新的爬取请求了。所以最下方 follows 按钮部分有 217 的数字标记,这代表新生成了 217 个爬取请求,同时这些请求的 URL 都呈现在当前页面了。 再点击下方的 web 按钮,即可预览当前爬取结果的页面,如图 12-9 所示。
图 12-9 预览页面 当前看到的页面结果和浏览器看到的几乎是完全一致的,在这里我们可以方便地查看页面请求的结果。 点击 html 按钮即可查看当前页面的源代码,如图 12-10 所示。
图 12-10 页面源码 如果需要分析代码的结构,我们可以直接参考页面源码。 我们刚才在 index_page() 方法中提取了所有的链接并生成了新的爬取请求。但是很明显要爬取的肯定不是所有链接,只需要攻略详情的页面链接就够了,所以我们要修改一下当前 index_page() 里提取链接时的 CSS 选择器。 接下来需要另外一个工具。首先切换到 Web 页面,找到攻略的标题,点击下方的 enable css selector helper,点击标题。这时候我们看到标题外多了一个红框,上方出现了一个 CSS 选择器,这就是当前标题对应的 CSS 选择器,如图 12-11 所示。
图 12-11 CSS 工具 在右侧代码选中要更改的区域,点击左栏的右箭头,此时在上方出现的标题的 CSS 选择器就会被替换到右侧代码中,如图 12-12 所示。
图 12-12 操作结果 这样就成功完成了 CSS 选择器的替换,非常便捷。 重新点击左栏右上角的 run 按钮,即可重新执行 index_page() 方法。此时的 follows 就变成了 10 个,也就是说现在我们提取的只有当前页面的 10 个攻略,如图 12-13 所示。
图 12-13 运行结果 我们现在抓取的只是第一页的内容,还需要抓取后续页面,所以还需要一个爬取链接,即爬取下一页的攻略列表页面。我们再利用 crawl() 方法添加下一页的爬取请求,在 index_page() 方法里面添加如下代码,然后点击 save 保存:
图 12-14 运行结果 现在,索引列表页的解析过程我们就完成了。
图 12-15 运行结果 切换到 Web 页面预览效果,页面下拉之后,头图正文中的一些图片一直显示加载中,如图 12-16 和图 12-17 所示。
图 12-16 预览结果
图 12-17 预览结果 查看源代码,我们没有看到 img 节点,如图 12-18 所示。
图 12-18 源代码 出现此现象的原因是 pyspider 默认发送 HTTP 请求,请求的 HTML 文档本身就不包含 img 节点。但是在浏览器中我们看到了图片,这是因为这张图片是后期经过 JavaScript 出现的。那么,我们该如何获取呢? 幸运的是,pyspider 内部对接了 PhantomJS,那么我们只需要修改一个参数即可。 我们将 index_page() 中生成抓取详情页的请求方法添加一个参数 fetch_type,改写的 index_page() 变为如下内容:
图 12-19 爬取详情 再点击新生成的详情页 Request 的爬取按钮,这时我们便可以看到页面变成了这样子,如图 12-20 所示。
图 12-20 运行结果 图片被成功渲染出来,这就是启用了 PhantomJS 渲染后的结果。只需要添加一个 fetch_type 参数即可,这非常方便。 最后就是将详情页中需要的信息提取出来,提取过程不再赘述。最终 detail_page() 方法改写如下所示:
图 12-21 输出结果 左栏中输出了最终构造的字典信息,这就是一篇攻略的抓取结果。
图 12-22 启动爬虫 在最左侧我们可以定义项目的分组,以方便管理。rate/burst 代表当前的爬取速率,rate 代表 1 秒发出多少个请求,burst 相当于流量控制中的令牌桶算法的令牌数,rate 和 burst 设置的越大,爬取速率越快,当然速率需要考虑本机性能和爬取过快被封的问题。process 中的 5m、1h、1d 指的是最近 5 分、1 小时、1 天内的请求情况,all 代表所有的请求情况。请求由不同颜色表示,蓝色的代表等待被执行的请求,绿色的代表成功的请求,黄色的代表请求失败后等待重试的请求,红色的代表失败次数过多而被忽略的请求,这样可以直观知道爬取的进度和请求情况,如图 12-23 所示。
图 12-23 爬取情况 点击 Active Tasks,即可查看最近请求的详细状况,如图 12-24 所示。
图 12-24 最近请求 点击 Results,即可查看所有的爬取结果,如图 12-25 所示。
图 12-25 爬取结果 点击右上角的按钮,即可获取数据的 JSON、CSV 格式。
图 12-1 pyspider 架构图 Scheduler 发起任务调度,Fetcher 负责抓取网页内容,Processer 负责解析网页内容,然后将新生成的 Request 发给 Scheduler 进行调度,将生成的提取结果输出保存。 pyspider 的任务执行流程的逻辑很清晰,具体过程如下所示。













在开始了解 X-Forward-For 之前,我们先来假设一个场景。你是一名爬虫工程师,现在要爬取目标网站 xxx.com 上面的内容。在编码的时候,你发现单位时间内请求频率过高时会被限制,猜测应该是目标网站针对 IP 地址做了限制。现在你有两种选择:
那么问题来了:
图 forwarded-client-server 由于客户端到代理 1 的请求没有使用代理,所以值为空或短横线。到代理 2 时,中间经过了代理 1,所以值为原始 IP。到服务端时,中间经过了代理 1 和代理2 ,所以值为原始 IP 和代理 1 IP。 上面就是关于 RFC7239 中部分内容的解读。看到这里,想必你已有丝丝头绪,接下来我们再捋一捋。
将代码片段 Forwarded-Test 中用于设置代理服务器 IP 和端口号的字段值改为高匿 IP 及对应的端口号即可,例如:






图 11-12 mitmproxy 运行结果 右下角会出现当前正在监听的端口。 或者启动 mitmdump,它也会监听 8080 端口,命令如下所示:
图 11-13 MitmDump 运行结果 将手机和 PC 连接在同一局域网下,设置代理为当前代理。首先看看 PC 的当前局域网 IP。 Windows 上的命令如下所示:
图 11-14 查看局域网 IP 一般类似 10... 或 172.16.. 或 192.168.1. 这样的 IP 就是当前 PC 的局域网 IP,例如此图中 PC 的 IP 为 192.168.1.28,手机代理设置类似如图 11-15 所示。
图 11-15 代理设置 这样我们就配置好了 mitmproxy 的的代理。
图 11-16 所有请求 这就相当于之前我们在浏览器开发者工具监听到的浏览器请求,在这里我们借助于 mitmproxy 完成。Charles 完全也可以做到。 这里是刚才手机打开百度页面时的所有请求列表,左下角显示的 2/38 代表一共发生了 38 个请求,当前箭头所指的是第二个请求。 每个请求开头都有一个 GET 或 POST,这是各个请求的请求方式。紧接的是请求的 URL。第二行开头的数字就是请求对应的响应状态码,后面是响应内容的类型,如 text/html 代表网页文档、image/gif 代表图片。再往后是响应体的大小和响应的时间。 当前呈现了所有请求和响应的概览,我们可以通过这个页面观察到所有的请求。 如果想查看某个请求的详情,我们可以敲击回车,进入请求的详情页面,如图 11-17 所示。
图 11-17 详情页面 可以看到 Headers 的详细信息,如 Host、Cookies、User-Agent 等。 最上方是一个 Request、Response、Detail 的列表,当前处在 Request 这个选项上。这时我们再点击 TAB 键,即可查看这个请求对应的响应详情,如图 11-18 所示。
图 11-18 响应详情 最上面是响应头的信息,下拉之后我们可以看到响应体的信息。针对当前请求,响应体就是网页的源代码。 这时再敲击 TAB 键,切换到最后一个选项卡 Detail,即可看到当前请求的详细信息,如服务器的 IP 和端口、HTTP 协议版本、客户端的 IP 和端口等,如图 11-19 所示。
图 11-19 详细信息 mitmproxy 还提供了命令行式的编辑功能,我们可以在此页面中重新编辑请求。敲击 e 键即可进入编辑功能,这时它会询问你要编辑哪部分内容,如 Cookies、Query、URL 等,每个选项的第一个字母会高亮显示。敲击要编辑内容名称的首字母即可进入该内容的编辑页面,如敲击 m 即可编辑请求的方式,敲击 q 即可修改 GET 请求参数 Query。 这时我们敲击 q,进入到编辑 Query 的页面。由于没有任何参数,我们可以敲击 a 来增加一行,然后就可以输入参数对应的 Key 和 Value,如图 11-20 所示。
图 11-20 编辑页面 这里我们输入 Key 为 wd,Value 为 NBA。 然后再敲击 esc 键和 q 键,返回之前的页面,再敲击 e 和 p 键修改 Path。和上面一样,敲击 a 增加 Path 的内容,这时我们将 Path 修改为 s,如图 11-21 所示。
图 11-21 编辑页面 再敲击 esc 和 q 键返回,这时我们可以看到最上面的请求链接变成了
图 11-22 请求详情 敲击 a 保存修改,敲击 r 重新发起修改后的请求,即可看到上方请求方式前面多了一个回旋箭头,这说明重新执行了修改后的请求。这时我们再观察响应体内容,即可看到搜索 NBA 的页面结果的源代码,如图 11-23 所示。
图 11-23 响应结果 以上内容便是 mitmproxy 的简单用法。利用 mitmproxy,我们可以观察到手机上的所有请求,还可以对请求进行修改并重新发起。 Fiddler、Charles 也有这个功能,而且它们的图形界面操作更加方便。那么 mitmproxy 的优势何在? mitmproxy 的强大之处体现在它的另一个工具 mitmdump,有了它我们可以直接对接 Python 对请求进行处理。下面我们来看看 mitmdump 的用法。
图 11-24 手机端页面 PC 端控制台输出如图 11-25 所示。
图 11-25 PC 端控制台 手机端返回结果的 Headers 实际上就是请求的 Headers,User-Agent 被修改成了 mitmproxy。PC 端控制台输出了修改后的 Headers 内容,其 User-Agent 的内容正是 mitmproxy。 所以,通过这三行代码我们就可以完成对请求的改写。print() 方法输出结果可以呈现在 PC 端控制台上,可以方便地进行调试。
图 11-26 运行结果 不同的颜色对应不同级别的输出,我们可以将不同的结果合理划分级别输出,以更直观方便地查看调试信息。
图 11-27 输出结果 结果中分别输出了请求链接、请求头、请求 Cookies、请求 Host、请求方法、请求端口、请求协议这些内容。 同时我们还可以对任意属性进行修改,就像最初修改 Headers 一样,直接赋值即可。例如,这里将请求的 URL 修改一下,脚本修改如下所示:
图 11-28 手机端页面 比较有意思的是,浏览器最上方还是呈现百度的 URL,但是页面已经变成了 httpbin.org 的页面了。另外,Cookies 明显还是百度的 Cookies。我们只是用简单的脚本就成功把请求修改为其他的站点。通过这种方式修改和伪造请求就变得轻而易举。 通过这个实例我们知道,有时候 URL 虽然是正确的,但是内容并非是正确的。我们需要进一步提高自己的安全防范意识。 Request 还有很多属性,在此不再一一列举。更多属性可以参考:
图 11-29 PC 端控制台 控制台输出了响应的状态码、响应头、Cookies、响应体这几部分内容。 我们可以通过 response() 方法获取每个请求的响应内容。接下来再进行响应的信息提取和存储,我们就可以成功完成爬取了。
图 11-1 Charles 运行界面 Charles 会一直监听 PC 和手机发生的网络数据包,捕获到的数据包就会显示在左侧,随着时间的推移,捕获的数据包越来越多,左侧列表的内容也会越来越多。 可以看到,图中左侧显示了 Charles 抓取到的请求站点,我们点击任意一个条目便可以查看对应请求的详细信息,其中包括 Request、Response 等内容。 接下来清空 Charles 的抓取结果,点击左侧的扫帚按钮即可清空当前捕获到的所有请求。然后点击第二个监听按钮,确保监听按钮是打开的,这表示 Charles 正在监听 App 的网络数据流,如图 11-2 所示。
图 11-2 监听过程 这时打开手机京东,注意一定要提前设置好 Charles 的代理并配置好 CA 证书,否则没有效果。 打开任意一个商品,如 iPhone,然后打开它的商品评论页面,如图 11-3 所示。
图 11-3 评论页面 不断上拉加载评论,可以看到 Charles 捕获到这个过程中京东 App 内发生的所有网络请求,如图 11-4 所示。
图 11-4 监听结果 左侧列表中会出现一个 api.m.jd.com 链接,而且它在不停闪动,很可能就是当前 App 发出的获取评论数据的请求被 Charles 捕获到了。我们点击将其展开,继续上拉刷新评论。随着上拉的进行,此处又会出现一个个网络请求记录,这时新出现的数据包请求确定就是获取评论的请求。 为了验证其正确性,我们点击查看其中一个条目的详情信息。切换到 Contents 选项卡,这时我们发现一些 JSON 数据,核对一下结果,结果有 commentData 字段,其内容和我们在 App 中看到的评论内容一致,如图 11-5 所示。
图 11-5 Json 数据结果 这时可以确定,此请求对应的接口就是获取商品评论的接口。这样我们就成功捕获到了在上拉刷新的过程中发生的请求和响应内容。
图 11-6 监听结果 这个结果和原本在 Web 端用浏览器开发者工具内捕获到的结果形式是类似的。 接下来点击 Contents 选项卡,查看该请求和响应的详情信息。 上半部分显示的是 Request 的信息,下半部分显示的是 Response 的信息。比如针对 Reqeust,我们切换到 Headers 选项卡即可看到该 Request 的 Headers 信息,针对 Response,我们切换到 JSON TEXT 选项卡即可看到该 Response 的 Body 信息,并且该内容已经被格式化,如图 11-7 所示。
图 11-7 监听结果 由于这个请求是 POST 请求,所以我们还需要关心的就是 POST 的表单信息,切换到 Form 选项卡即可查看,如图 11-8 所示。
图 11-8 监听结果 这样我们就成功抓取 App 中的评论接口的请求和响应,并且可以查看 Response 返回的 JSON 数据。 至于其他 App,我们同样可以使用这样的方式来分析。如果我们可以直接分析得到请求的 URL 和参数的规律,直接用程序模拟即可批量抓取。
图 11-9 编辑页面 我们可以将 Form 中的某个字段移除,比如这里将 partner 字段移除,然后点击 Remove。这时我们已经对原来请求携带的 Form Data 做了修改,然后点击下方的 Execute 按钮即可执行修改后的请求,如图 11-10 所示。
图 11-10 编辑页面 可以发现左侧列表再次出现了接口的请求结果,内容仍然不变,如图 11-11 所示。
图 11-11 重新请求后结果 删除 Form 表单中的 partner 字段并没有带来什么影响,所以这个字段是无关紧要的。 有了这个功能,我们就可以方便地使用 Charles 来做调试,可以通过修改参数、接口等来测试不同请求的响应状态,就可以知道哪些参数是必要的哪些是不必要的,以及参数分别有什么规律,最后得到一个最简单的接口和参数形式以供程序模拟调用使用。
图 10-7 返回数据 但是,这个接口在没有登录的情况下会有请求频率检测。如果一段时间内访问太过频繁,比如打开这个链接,一直不断刷新,则会看到请求频率过高的提示,如图 10-8 所示。
图 10-8 提示页面 如果重新打开一个浏览器窗口,打开
图 10-9 对比页面 图中左侧是登录了账号之后请求接口的结果,右侧是未登录账号请求接口的结果,二者的接口链接是完全一样的。未登录状态无法正常访问,而登录状态可以正常显示。 因此,登录账号可以降低被封禁的概率。 我们可以尝试登录之后再做爬取,被封禁的几率会小很多,但是也不能完全排除被封禁的风险。如果一直用同一个账号频繁请求,那就有可能遇到请求过于频繁而封号的问题。 如果需要做大规模抓取,我们就需要拥有很多账号,每次请求随机选取一个账号,这样就降低了单个账号的访问频率,被封的概率又会大大降低。 那么如何维护多个账号的登录信息呢?这时就需要用到 Cookies 池了。接下来我们看看 Cookies 池的构建方法。
图 10-10 Cookies 池架构 Cookies 池架构的基本模块分为 4 块:存储模块、生成模块、检测模块和接口模块。每个模块的功能如下。
图 10-11 用户名密码 Hash 结构
图 10-12 用户名 Cookies Hash 结构 Hash 的 Key 就是账号,Value 对应着密码或者 Cookies。另外需要注意,由于 Cookies 池需要做到可扩展,存储的账号和 Cookies 不一定单单只有本例中的微博,其他站点同样可以对接此 Cookies 池,所以这里 Hash 的名称可以做二级分类,例如存账号的 Hash 名称可以为 accounts:weibo,Cookies 的 Hash 名称可以为 cookies:weibo。如要扩展知乎的 Cookies 池,我们就可以使用 accounts:zhihu 和 cookies:zhihu,这样比较方便。 好,接下来我们就创建一个存储模块类,用以提供一些 Hash 的基本操作,代码如下:
图 10-13 接口页面 爬虫只需要请求该接口就可以实现随机 Cookies 的获取。
图 10-1 开发者工具设置 点击登录按钮,这时便会看到开发者工具下方显示了各个请求过程,如图 10-2 所示。
图 10-2 请求过程 点击第一个请求,进入其详情页面,如图 10-3 所示。
图 10-3 详情页面 可以看到请求的 URL 为
图 10-4 详情页面 Headers 里面包含了 Cookies、Host、Origin、Referer、User-Agent 等信息。Form Data 包含了 5 个字段,commit 是固定的字符串 Sign in,utf8 是一个勾选字符,authenticity_token 较长,其初步判断是一个 Base64 加密的字符串,login 是登录的用户名,password 是登录的密码。 综上所述,我们现在无法直接构造的内容有 Cookies 和 authenticity_token。下面我们再来探寻一下这两部分内容如何获取。 在登录之前我们会访问到一个登录页面,此页面是通过 GET 形式访问的。输入用户名密码,点击登录按钮,浏览器发送这两部分信息,也就是说 Cookies 和 authenticity_token 一定是在访问登录页的时候设置的。 这时再退出登录,回到登录页,同时清空 Cookies,重新访问登录页,截获发生的请求,如图 10-5 所示。
图 10-5 截获请求 访问登录页面的请求如图所示,Response Headers 有一个 Set-Cookie 字段。这就是设置 Cookies 的过程。 另外,我们发现 Response Headers 没有和 authenticity_token 相关的信息,所以可能 authenticity_token 还隐藏在其他的地方或者是计算出来的。我们再从网页的源码探寻,搜索相关字段,发现源代码里面隐藏着此信息,它是一个隐藏式表单元素,如图 10-6 所示。
图 10-6 表单元素 现在我们已经获取到所有信息,接下来实现模拟登录。
由于这种验证码交互形式比较友好,且安全性、美观度上也会更高,像这种类似的验证码也变得越来越流行。另外不仅仅是「极验」,其他很多验证码服务商也推出了类似的验证码服务,如「网易易盾」等,上图所示的就是「网易易盾」的滑动验证码。 没错,确实这种滑动验证码的出现让很多网站变得更安全。但是做爬虫的可就苦恼了,如果采用自动化的方法来绕过这种滑动验证码,关键部分在于以下两点:
我们想知道这只狗在哪,它的舌头在哪,找到了就把它们框选出来,这就是目标检测。 经过目标检测算法处理之后,我们期望得到的图片是这样的:
可以看到这只狗和它的舌头就被框选出来了,这就完成了一个不错的目标检测。 现在比较流行的目标检测算法有 R-CNN、Fast R-CNN、Faster R-CNN、SSD、YOLO 等,感兴趣同学的可以了解一下,当然看不懂也没有什么影响。 另外再提一个地方,不懂深度学习的同学可以看看,懂的直接跳过下面一段。 我们既然要搭建一个模型来实现一个目标检测算法,那模型怎么知道我们究竟想识别个什么东西?就比如上图,模型咋知道我们想识别的是狗而不是草,是舌头而不是鼻子。这是因为,既然叫深度学习,那得有学习的东西。所以,搭建一个深度学习模型需要训练数据。啥也不告诉模型,模型从哪里去学习?所以,我们得预先有一些标注好位置的图片供模型去学习(训练),比如准备好多张狗的图片和狗的轮廓标注位置,模型在训练过程中会自动学习到图片和标注位置的关系。模型训练好了之后,我们给模型一个没有见过的类似的狗的图,模型也能找出来目标的位置了。 所以,迁移到验证码缺口识别这个任务上来,我们第一步就是给模型提供一些训练数据,训练数据就包括验证码的图片和缺口的位置标注轮廓信息。 好,既然如此,我们第一步就得准备一批验证码数据供标注和训练了。
我们不需要滑轨的部分,只保留验证码本身的图片和上面的两个缺口就行了,下面是我准备的一些验证码图:
我爬了大约上千张吧,越多越好。当然对于今天的任务来说,其实几十上百张已经就够了。
它已经内置了一些深度学习模型,包括图像分类、物体检测、预测分析等等,我们可以直接利用它们来快速搭建属于自己的模型。 在这里我们就切换到「自动学习」的选项卡,创建一个物体检测的项目。
进入项目里面,可以看到最上面会显示三个步骤:
那我们先来第一步——数据标注,这里我把一些验证码的图上传到页面中,在这里我上传了 112 张图:
上传完毕之后我们可以点击每一张图片进行标注了,这个平台提供了非常方便的标注功能,只需要鼠标拖拽个轮廓就完成了,112 张图标注完也就几分钟,标注的时候就框选这么个轮廓就行了,如图所示:
在这里边界需要把整个缺口的图全框选出来,其中上边界和右边界和标注框相切即可,总之确保标注框正好把缺口图框选出来就行,平台会自动保存和记录标注的像素点位置。 标注完一个,它会提示要添加一个名字,我在这里添加的名字叫「边界」,可以随意指定。 等全部标注完毕,点击「保存并返回」按钮即可。
在这里,我们只需要设置一下「最大训练时长」就好了,这么点图片其实几分钟就能训练完了,「最大训练时长」随意填写即可,最小不小于 0.05,填写完了之后就可以点击「开始训练」按钮训练了。 等几分钟,就会训练完成了,可以看到类似如图的页面:
这里显示了模型的各个参数和指标。 是的,你没看错,我们没有写任何代码,只过了几分钟,模型就已经训练完,并且可以部署上线了。
过一会儿, 部署成功之后便可以看到类似这样的界面:
在这里我们可以上传任意的验证码图片进行测试,比如我随意上传一张没有标注过的验证码图,然后它会给我们展示出预测结果,如图所示:
可以看到,它就把缺口的位置检测出来了,同时在右侧显示了具体的像素值和置信度:
AI 在图像识别和文本处理方面的效果尤为突出,且已经应用到人类的生活中,例如人脸识别、对话、车牌识别、城市智慧大脑项目中的目标检测和目标分类等。
接下来,我们将了解图像分类的需求、完成任务的前提条件和任务实践。
目标检测,指的是检测目标在图片中的位置。例如智慧交通项目中,路面监控摄像头拍摄画面中车辆的位置。目标检测涉及两种技术:分类和定位。也就是说先判定图片中是否存在指定的目标,然后还需要确定目标在图片中的位置。
这样的技术将会应用在人脸识别打卡、视频监控警报、停车场、高速收费站和城市智慧交通等项目当中。
简单来说,AI 工程师必须准备很多张不同的图片,并且将一大部分图片中的目标标注出来,然后让计算机提取每张图片中的特征,最后就会形成「认知」。 想一想,你还小的时候,是如何分辨鸭子和鹅的呢?
是不是根据它们的特征进行判断的?
AI 在硬件、软件、数据资料和人才方面都是很费钱的,普通的 IT 工程师也就是学习了解一下,远远达不到产品商用的要求。 普通的中小企业,极少有资质和经济能力吸引高学历且优秀的 AI 工程师,这就导致了资源的聚拢和倾斜。 想要将图像分类技术商用,在让计算机经历「看」、「认识」的步骤并拥有「分辨」能力后,还要将其转换为 Web 服务。
但我只想将人脸识别或者图像分类的功能集成到我的项目当中,就那么困难吗? 我只是一个很小的企业,想要在原来普通的视频监控系统中增加「家人识别」、「陌生人警报」、「火灾警报」和「生物闯入提醒」等功能,没有上述的条件和经济投入,就不能实现了吗? 我好苦恼! 有什么好办法吗?
它为机器学习与深度学习提供海量数据预处理及半自动化标注、大规模分布式 Training、自动化模型生成,及端-边-云模型按需部署能力,帮助用户快速创建和部署模型,管理全周期 AI 工作流。 它为用户提供了以下可选模式:
同时,它将 AI 开发的整个过程都集成了进来。例如数据标注、模型训练、参数优化、服务部署、开放接口等,这就是「全周期 AI 工作流」。
还有,平台上的操作都是可视化的。 这些条件对于想要将 AI 技术应用于产品,但无奈条件不佳的个人开发者和企业提供了机会,这很重要!可以说
想想就美滋滋,太棒了! 赶紧体验一下!
进入到
区域 2 自动学习模式中有图像分类,将鼠标移动到图标上,并点击弹出的「开始体验」按钮。如果是华为云的新用户,网页会提示我们输入访问密钥和私有访问密钥。
没有密钥的开发者可以点击页面给出的链接并按照指引获取密钥,得到两种密钥后将其填入框中,点击「确定」按钮即可。 此时正式进入项目创建流程中,点击「图像分类」中的「创建项目」按钮(华为云为用户准备了对应的教程,很贴心)。
在创建项目的页面中,我们需要填两三项配置。要注意的是,项目是按需计费的,这次我们只是体验,也没有训练和存储太多数据,所以费用很低,大家不用担心。 项目名称可以根据需求设定一个容易记的,案例中我将其设定为 ImageCLF-Test-Pro。在训练数据的存储选择处,点击输入框中的文件夹图标,在弹出的选项卡中新建 obs 桶
并在创建的桶中新建文件夹
最后输入描述,并点击页面右下角的「创建项目」按钮即可。
当然,数据越多、形态越丰富、标注越准确,那么训练结果就会越好,AI 服务的体验就会越好。 这里我准备了一些直升机、坦克和狗的图片,共 45 张。
依次将 3 类图片标注后,左侧图片标注的「未标注」选项卡中的图就会清空,而「已标注」选项卡中可以看到标注好的图片。
这个我们不必理解它的作用,可以按照默认值进行,也可以稍微调整,例如将训练时长的上限改为 0.2。
训练页左侧会显示训练状态,例如初始化、运行中和运行成功/失败等。训练完成后,右侧会给出运行时长、准确率、评估结果和训练参数等信息。 
稍微等待些许时间(本次约 10 分钟)后,页面提示部署完成,同时页面将会分为 3 栏。
左侧 1 区为部署状态和控制。中间 2 区可以在线测试图片分类,右侧 3 区会显示在线测试的结果(包括准确率),右侧 4 区提供了 API 接口,方便我们将其集成到 Web 应用当中。
1 秒中不到,右侧 3 区就会返回本次预测的结果:
返回的预测结果如下:
返回的预测结果如下:
图 9-21 搜索结果 点击搜索后,搜索结果的 URL 中其实有很多无关 GET 请求参数,将无关的参数去掉,只保留 type 和 query 参数,例如
图 9-22 翻页列表 注意,如果没有输入账号登录,那只能看到 10 页的内容,登录之后可以看到 100 页内容,如图 9-23 和图 9-24 所示。
图 9-23 不登录的结果
图 9-24 登录后的结果 如果需要爬取更多内容,就需要登录并使用 Cookies 来爬取。 搜狗微信站点的反爬虫能力很强,如连续刷新,站点就会弹出类似如图 9-25 所示的验证。
图 9-25 验证码页面 网络请求出现了 302 跳转,返回状态码为 302,跳转的链接开头为
图 9-26 代理池运行结果 这样,数据库中留下的 100 分的代理就是针对搜狗微信的可用代理了,如图 9-27 所示。
图 9-27 可用代理列表 同时访问代理接口,接口设置为 5555,访问
图 9-28 代理接口 再定义一个函数来获取随机代理:
图 9-29 运行结果 程序首先调度了第一页结果对应的请求,获取了代理执行此请求,随后得到了 11 个新请求,请求都是 WeixinRequest 类型,将其再加入队列。随后继续调度新加入的请求,也就是文章详情页对应的请求,再执行,得到的就是文章详情对应的提取结果,提取结果是字典类型。 程序循环往复,不断爬取,直至所有结果爬取完毕,程序终止,爬取完成。 爬取结果如图 9-30 所示。
图 9-30 爬取结果 我们可以看到,相关微信文章都已被存储到数据库里了。

很多岗位都要求有逆向或者解决反爬虫的能力
甚至作为优先选择的条件 

声明: 本次活动最终解释权归内容制作方夜幕团队所有。
也可以在它的基础上做更进一步的统计分析。
这次将详细目录呈现给大家。请大家先阅读《Python3 反爬虫原理与绕过实战》的内容提要
作者韦世东是资深爬虫工程师,2019年华为云认证云享专家、掘金社区优秀作者、GitChat认证作者、搜狐产品技术约稿作者、夜幕团队成员。拥有七年互联网从业经验,擅长反爬虫的设计和绕过技巧。

这时候,如果我们能有一个客户端,即 Window 上的 exe 程序或 Mac 上的 app 应用程序,它们的名字就叫做 GitHub、微信公众平台等等,打开之后只单独负责呈现 GitHub、微信公众号的内容,我们就可以免去在浏览器中来回寻找站点和切换站点的麻烦。 甚至说,在 Windows 上我们可以直接把这个应用放在桌面或把它 Pin 到任务栏上, Mac 上我们可以直接将它固定到 Dock 栏上,这样一键就打开了,省时省力。如果使用了快捷启动软件,比如 Wox (Windows)或 Alfred(Mac),直接输入 GitHub 或者微信公众平台,那就更方便唤出了,简直不要太方便。 而且,我个人感觉,用客户端软件比用网页更有一种「踏实感」,不知道大家会不会也有这种感觉。 所以,如果能将这些常用的或者重度依赖的网站转成客户端软件,那就再方便不过了。 比如我用的是 Mac,把 GitHub 转成客户端软件之后,我习惯性用 Alfred 呼出:
然后就打开了一个 GitHub.app:
然后把它固定到 Dock 栏上:
就仿佛拥有了一个 GitHub 的客户端,功能与网页一模一样,再也不用在浏览器里面切来切去。而且也不用担心版本更新的问题,因为它就是开了一个独立的网页,网页改版或者更新,内容就随着更新。 是不是很方便呢? 如果你觉得是,那就随着我来了解一下怎样实现吧。
怎样,不论是什么网页,就可以使用它来转换成一个客户端软件。 另外它支持三大操作系统,Windows、Linux、Mac,即用它可以将网页转成
这个名字有点奇怪,我们可以使用命令的一个选项即可控制生成的客户端的名称,添加一个 name 参数即可:
另外我们可以看到客户端的图标也自动生成了,这个图标怎么来的呢?这个是用的 nativefier 维护的 icons,恰好 GitHub 在它们的收录范围内,所以就用上了。这些 icons 也是一个公开的 Repository,链接为:
然后把图片使用下面的命令就可以自定义图标了:
好了,这就是基本的用法,其实大部分情况只需要这几个参数就够了,如果想了解功能大家可以参考官方的 API 文档:
于是,这篇《利用 GitHub 从零开始搭建一个博客》的文章就诞生了。
这样一个博客的架子就出来了,我们只用了三个命令就完成了。
这时候我们去 GitHub 上面看看 Hexo 上传了什么内容,打开之后可以看到 master 分支有了这样的内容:
仔细看看,这实际上是博客文件夹下面的 public 文件夹下的所有内容,Hexo 把编译之后的静态页面内容上传到 GitHub 的 master 分支上面去了。 这时候可能就有人有疑问了,那我博客的源码也想放到 GitHub 上面怎么办呢?其实很简单,新建一个其他的分支就好了,比如我这边就新建了一个 source 分支,代表博客源码的意思。 具体的添加过程就很简单了,参加如下命令:

另外还有几个可选项,比如:
将其放置到 themes/next/source/images/avatar.png 路径,然后在主题 _config.yml 文件下编辑 avatar 的配置,修改为正确的路径即可。
配置完成之后就会显示头像。
修改后的代码样式:
嗯,个人觉得整体看起来逼格高了不少。
可以看到在页面右上角显示了 GitHub 的图标,点击可以进去到 Repository 页面。
GitHub 授权登录之后就可以使用了,评论的内容会自动出现在 Issue 里面。
可以看到左侧导航也出现了标签,点击之后右侧会显示标签的列表。
下面有个 custom domain 的选项,输入你想自定义的域名地址,然后添加 CNAME 解析就好了。 另外下面还有一个 Enforce HTTPS 的选项,GitHub Pages 会在我们配置自定义域名之后自动帮我们配置 HTTPS 服务。刚配置完自定义域名的时候可能这个选项是不可用的,一段时间后等到其可以勾选了,直接勾选即可,这样整个博客就会变成 HTTPS 的协议的了。 另外有一个值得注意的地方,如果配置了自定义域名,在目前的情况下,每次部署的时候这个自定义域名的设置是会被自动清除的。所以为了避免这个情况,我们需要在项目目录下面新建一个 CNAME 文件,路径为 source/CNAME,内容就是自定义域名。 比如我就在 source 目录下新建了一个 CNAME 文件,内容为:


这时候,我想到了一个东西——Docker,它可以用来解决这个问题! 因为 Docker 容器被创建后,不管外界的网卡有多少个,容器内部的网卡都只会有一个Docker自己的虚拟网卡(容器间通信用的)和一个本地环回接口(不用管它),而且我们在容器内进行拨号操作时,产生的那个新的虚拟网卡也不会影响到外界或其他容器,这样的话,代理服务器就不需要指定网卡了,直接启动就能跑! 那么现在整个流程就跑通了,进入实际操作环节看看吧!
可以看到,如前文所述,现在有三个网卡,一个是 Docker 自己的、一个是本地环回接口(这个不用管)、一个是拨号产生的虚拟网卡。
文章在某个位置会渐变隐藏,同时浮现一个公众号的样子,需要扫码才能解锁。这时候读者扫码自动关注了公号,博客文章也自然而然地解锁,这样博客的读者就自然关注到公号上面来了。
好,既然隐藏了,那么下面就加个提示吧,把公众号的二维码先放上,然后把那个 Session ID 放上,提示用户关注公众号后发送这个 ID 就能解锁了,但这个 ID 又不能太长,多少呢?六位吧那就。 类似做成这样的样子:
好,那么这个 ID 怎么获取的呢? 刚才说了,从 Cookies 里面获取就行了,找那个能够标识 Session ID 的一个 Cookies 字段,然后摘取其值的其中几位就行了,摘取的位置也有讲究,前几位仿佛重复率很高的样子,后面几位几乎不重复,那就截取最后六位数字吧。 好,然后我就在博客里面加了这么一点 JavaScript 代码来实现这个 ID 的提取:
然后关注了公号,发送了代码:
发送完毕之后,大约一两秒之后,抬头看看博客,就是这个样子了:
这已经就完成了解锁和转化,读者可以全站永久解锁我的博客文章,我也增长了粉丝。 现在过一段时间就会有读者发来代码解锁,同时成为了我的粉丝,订阅号助手看到消息如下:
以上便是这个博客转化的思路分享和实现,大家也可以到我的博客体验一下,谢谢!




我们的目的就是它的正文、标题等内容。下面我们用 Readability 试一下,示例如下:
下面用一个实例来感受一下:
看看运行结果:
视频换脸技术大家应该早有耳闻,但这个软件有点意思,它抓住了几个点使得它一炮而红。 第一是这个软件的效果确实不错,我拿自己也做了实验,发现确实它渲染的一些结果几乎毫无违和感,毕竟这个软件核心拼的就是技术。 第二这个软件贴近于日常生活,我们可以把自己的照片上传,让我们真正成为视频里的主角。另外视频选材很有讲究,都是一些剪辑过的明星精彩镜头,这样我们生成的视频镜头会让我们有变成明星的感觉,非常有代入感。
一般来说一张脸会用 68 个点来标记出来,每识别的模型接收一张人脸图像,输出这 68 个点的坐标,这样我们就可以实现人脸定位了。 现在现成的模型也很多了,比如 dlib,opencv 等开源工具包可以直接拿来使用了,如果要更精准地话可以使用更复杂的卷积神经网络模型来实现,大家可以了解下相关论文。
这时候我自然而然想到,既然用的是 3D 结构光摄像头,那么如果用了 3D 打印技术把一个人的肖像打印出来,或者用一个非常逼真的蜡像来进行刷脸识别,能不能通过呢?我看了一些报道,发现不少案例的确通过了刷脸测试,比如解开了 iPhone 面部识别锁等等。但要通过 3D 打印技术来模拟一个人的肖像成本还是蛮高的,所以基本上也不太会有人来搞这些。 如果对此还心有余悸的话,支付宝还回应称,即便是真的被盗刷了,支付宝也会通过保险公司进行全额赔付。 所以基本上是不用担心其安全性的,尤其是 Zao 这个软件的出现是没有对刷脸支付的风险造成大的影响的,其就是增加了一个活体视频模拟的实现,对刷脸支付的安全性没有出现大的突破性威胁。
我们通过几个例子来看看 MySQL 与 MongoDB 的差异。 与 MySQL 数据库不同的是,MongoDB 不需要预先定义表和字段,这正是它灵活性的体现。MongoDB 可以拥有多个数据库,每个数据库可以拥有多个集合,每个集合可以存储多份文档,这种关系与 SQL 数据库中的“数据库、表、数据”相当。下图描述了 MongoDB 中数据库、集合和文档的关系:
描述节主点掉线,重新选举主节点的图
如果你觉得有学习 MongoDB 的需要,且这篇文章规划的内容是你想要的内容,那么请长按下方图片识别二维码,前往订阅文章吧! 