docker-compose 中 links 和 depends_on 区别

以下的内容适用于 docker-compose 版本 version 2version 3。先来看 Docker 官方文档中关于 Docker Compose and Django的例子,可以使用 depends_on 来访问容器中的数据

version: '2'
services:
  db:
    image: postgres
  web:
    build: .
    command: python manage.py runserver 0.0.0.0:8000
    volumes:
      - .:/code
    ports:
      - "8000:8000"
    depends_on:
      - db

而比如下面使用

web:
  links:
   - db

则表示当启动 db 容器时会随机分配一个本地端口比如32777来连接容器3306端口,每一次修改或者重启容器都会改变该端口,使用 links 来保证每一次都能够连接数据库,而不需要知道具体端口是什么。比如说启动了一个 mysql 容器

docker run -d --name=my-mysql --env="MYSQL_ROOT_PASSWORD=mypassword" -P mysql
docker inspect <container-id> | grep HostPort

会显示该容器的本地端口。

docker-compose 执行 V2 文件时会自动在容器间创建一个网络,每一个容器都能够立即通过名字来访问到另外的容器。 因此,不再需要 links,links 过去通常用来开始db容器和web server容器网络之间的通讯,但是这一步已经被 docker-compose 做了。1

当使用 depends_on 来定义服务之间的依赖关系时会造成下面的影响2

  • docker-compose up 会依据依赖顺序启动服务
  • docker-compose up 启动时 SERVICE 会自动包括 SERVICE 的依赖

看这个例子:

version: '2'
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres

这个例子中 db ,redis 容器启动顺序要优先于 web 容器;当启动 web 容器时会自动创建 redis 和 db 容器。

不过需要注意的是, depends_on 不会等到 db 和 redis 容器 ready 再启动,web 容器仅仅等到 redis 和 db 容器启动就开始启动。具体可参考官网启动顺序了解。

reference


2018-03-12 docker , docker-compose , linux

使用 privoxy 转发 socks 到 http

Privoxy 是一款不进行网页缓存且自带过滤功能的代理服务器,本文主要使用其 socks 转 http 代理的功能。Privoxy 也能够过滤网页内容,管理 Cookie,控制访问,去广告、横幅、弹窗等等,因此可以作为广告过滤。

Privoxy is a non-caching web proxy with advanced filtering capabilities for enhancing privacy, modifying web page data and HTTP headers, controlling access, and removing ads and other obnoxious Internet junk. GNU GPLv2 开源

因为 shadowsocks,v2ray 都是将代理转为本地 socks5 代理,所以如果需要使用 http 代理,就需要借助 Privoxy 。如果只需要在本地启用 http 代理,也可以使用 proxychains

安装及配置

在 Linux 下安装非常简单

sudo apt install privoxy

默认的配置文件地址在 /etc/privoxy/config 目录下。假设本地 1080 端口已经启动(不管是本地 sslocal 还是 v2ray 本地都需要启动)然后要将本地 1080 socks5 代理转成 http 代理,重要的配置只有两行

# 把本地 HTTP 流量转发到本地 1080 SOCKS5 代理
forward-socks5t / 127.0.0.1:1080 .
# 可选,默认监听本地连接
listen-address 127.0.0.1:8118

如果想要将 http 代理非常到局域网中,可以使用 listen-address 0.0.0.0:8118。 Privoxy 默认的端口为 8118,可以自行修改。修改完成保存之后使用如下命令启动

sudo /etc/init.d/privoxy start
sudo /etc/init.d/privoxy reload   # 不重启服务的情况下重新加载配置

可以在终端进行测试 export http_proxy=http://127.0.0.1:8118 && curl ip.gs 应该显示代理的 IP 地址。如果监听 0.0.0.0:8118 ,那么局域网中,使用 ip:8118 也能够使用该 HTTP 代理,并且所有的流量都经由 HTTP 转发到 SOCKS5 代理,并走 shadowsocks 或者 v2ray 到墙外。

使用浏览器配置 HTTP 代理,然后访问 http://p.p 如果看到 Privoxy 启动成功表示一切 OK。

无法启动或启动错误

当启动 sudo /etc/init.d/privoxy start 时出现如下错误:

systemctl status privoxy.service
● privoxy.service - Privacy enhancing HTTP Proxy
Loaded: loaded (/lib/systemd/system/privoxy.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Sun 2018-03-11 17:49:40 CST; 4s ago
Process: 23666 ExecStopPost=/bin/rm -f $PIDFILE (code=exited, status=0/SUCCESS)
Process: 23668 ExecStart=/usr/sbin/privoxy --pidfile $PIDFILE --user $OWNER $CONFIGFILE (code=exited, status=1/FAILURE)
Main PID: 21029 (code=exited, status=15)

Mar 11 17:49:39 VM-145-149-ubuntu systemd[1]: Stopped Privacy enhancing HTTP Proxy.
Mar 11 17:49:39 VM-145-149-ubuntu systemd[1]: Starting Privacy enhancing HTTP Proxy...
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Control process exited, code=exited status=1
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: Failed to start Privacy enhancing HTTP Proxy.
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Unit entered failed state.
Mar 11 17:49:40 VM-145-149-ubuntu systemd[1]: privoxy.service: Failed with result 'exit-code'.

绝大部分情况下是配置文件错误,仔细检查 /etc/privoxy/config 文件,是否有重复配置,或者输入错误。

Privoxy 配置

其核心配置文件在 /etc/privoxy/config 文件中,之前配置过转发 socks 流量就在该文件中,不过通常情况下会需要修改另外两类文件:

  • action 文件,其中又包括 match-all.actiondefault.actionuser.action
  • filter 文件,其中包括 default.filteruser.filter

match-all.actiondefault.actiondefault.filter 建议不要修改, Privoxy 升级时会覆盖掉,自定义内容可以放入 user.actionuser.filter 文件中。

action 文件定义 Privoxy 动作,比如

{+block{禁止访问垃圾百度}}
.baidu.com

{+block} 是一个动作,block 后面的 {} 注释,可省略; .baidu.com 是上述动作对象,分为两个部分,host 和 path, host 部分支持通配符,path 部分指的是 / 后部分网址,支持 POSIX 1003.2 正则表达式。更加具体的可以参考官网文档。上述配置生效之后 baidu.com 的任何请求都会返回 403 。

filter 文件定义过滤响应的规则,比如

FILTER: replaceText 替换文本
s|网易|Google|g

FILTER 大写表示定义过滤规则, replaceText 表示规则名称,后面接注释;第二行定义具体规则,如果使用过 vi 或者 sed 工具,那么一定很熟悉这个 s 替换命令。

定义了 user.filter 过滤规则之后,需要在 user.action 文件中应用规则

{+filter{replaceText}}
.163.com

这样访问 163.com 网站中任何带有“网易”的字都会被替换为 Google,当然如果网页启用了 HTTPS,那么 Privoxy 也无能为力。Privoxy 唯一能够对 HTTPS 网站做的就是 block 了。这也就意味着屏蔽 HTTPS 网站页面内广告的能力下降了。

当前 Privoxy 配置的 action 和 filter 文件可以在代理情况下访问 http://config.privoxy.org/show-status 这个网址查看到。

广告屏蔽

前面也提到过 Privoxy 的广告过滤,不过需要注意的是使用去广告功能可能丢失一定的匿名性 1

下载 user.actionuser.filter 两个文件分别替换 /etc/privoxy/ 目录下的默认文件,重启 Privoxy 。

reference


2018-03-11 linux , proxy , socks , http , proxychains

查看Docker容器的日志

系统运行一段时间之后难免容器会出现问题,出现问题并不可怕,可怕的是不知道问题出现在哪里,这个时候查看当前容器运行的日志就能够排查出一些问题。

在之前的文章中,学会了如何创建,查看,移除等等管理容器的方法,其实查看日志也和这些方法类似。

比如要查看容器所有运行的日志可以使用

docker logs [containerId]

如果要持续观察容器的日志输出,可以使用 -f 或者 --follow 参数

docker logs -f [containerId]

但是这个命令时灵时不灵,在不同系统上,有的时候会打印出全部的日志,就和没加 -f 参数一样,所以有的时候要查看日志最末尾几行可以使用 --tail

docker logs --tail 100 [containerId]

如果想要查看某个时间之后的日志,可以使用 --since

docker logs --since 2018-05-01 [containerId]

同理如果要查看直到某个时间之前的日志也可以使用 --until

docker logs --until 2018-05-01 [containerId]

2018-03-10 docker , logs , linux

挂载腾讯云对象存储COS

腾讯云对象存储 Cloud Object Storage ,简称 COS,是腾讯云为企业和个人开发者提供的存储海量数据的分布式存储服务。

基本使用

控制面板申请对象存储基本信息,创建存储桶,输入名字,选择地域,选择访问权限,然后访问秘钥,可以得到如下信息:

bucket: backup-1251234567 (格式为 bucketname-appid)
SecretId: SecretId
SecretKey: SecretKey
region: ap-beijing
appid: 123456789

将腾讯云 COS 挂载到腾讯云服务器中

安装必要的应用

wget https://github.com/tencentyun/cosfs/releases/download/v1.0.2/cosfs_1.0.2-ubuntu16.04_amd64.deb
sudo apt update && sudo apt install gdebi-core
sudo gdebi release-cosfs-package

配置文件,设置 bucket name, access key/id 等信息,将其存放在 /etc/passwd-cosfs 文件中,文件权限设置为 640

echo my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-cosfs
chmod 640 /etc/passwd-cosfs

然后将 cos bucket mount 到指定目录

cosfs appid:bucket-name mount-point -ourl=my-cos-endpoint -odbglevel=info -oallow_other

这里的 cos-endpoint 不同地区不一样,比如北京是 http://cos.ap-beijing.myqcloud.com,其他地区根据 region 不同设置不同值

项目可参考官方项目

COSFS 工具使用


2018-03-09 linux , cos , tencent , vps , cloud-storage

Flask 使用模板渲染

Flask 使用 Jinja2 模板引擎。

Flask 会在 templates 文件夹里寻找模板。所以,如果你的应用是个模块,这个文件夹应该与模块同级;如果它是一个包,那么这个文件夹作为包的子目录:

情况 1: 模块:

/application.py
/templates
    /hello.html

情况 2: 包:

/application
    /__init__.py
    /templates
        /hello.html

使用 Jinja2 只需要使用 render_template() 方法

from flask import render_template

@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
    return render_template('hello.html', name=name)

模板支持继承

继承模式具体可参考: http://docs.jinkan.org/docs/flask/patterns/templateinheritance.html#template-inheritance

reference


2018-03-07 linux , flask , template , jinja2

使用 Docker hub 及其他镜像站点加速下载

有很多网站可以托管 Docker 镜像, Docker 官方站点 hub.docker.com 速度在国内访问不是很快,不过幸好国内有公司做了 hub.docker.com 的镜像,通过 CDN 优化了下载。Docker Hub 为用户提供无限数量的公开镜像托管服务,但是仅提供一个私有镜像托管。Docker Hub 上镜像分为两类,一类为官方镜像,ubuntu,redis 等等由权威三方开发和维护通过 Docker 官方认证,另一类就是普通用户镜像。

使用 registry mirrors

手动修改 Docker 配置 /etc/docker/daemon.json 文件

{
    "registry-mirrors": [
        "加速地址"
    ],
    "insecure-registries": []
}

修改其中的 加速地址,不同的服务提供的镜像加速地址不一样。记得修改配置之后 sudo /etc/init.d/docker restart 重启 docker。下面就总结一下国内的 Docker 镜像站点。

DaoCloud

DaoCloud 提供 的加速地址:

http://6ce28dce.m.daocloud.io

这个地址不同用户看起开不一样,可以使用我的,也可以自己注册。

这个地址不知道是不是长久地址,不过失效,可以到他的官方网站 查看。

Docker cn

也可以使用 Docker 官方提供的镜像

https://registry.docker-cn.com

官网地址

个人维护的镜像

mritd 反向代理了主流的三大仓库(Docker Hub,gcr.io,quay.io)。

docker registries

不得不说的 hub.docker.com,官方提供

daocloud hub

这是国内 DaoCloud 公司提供的

  • https://hub.daocloud.io/

gcr.io

可以通过下面的链接查看 gcr.io 中存在镜像,类似于直接在 https://hub.docker.com/ 中搜索查看。

阿里云

这里是阿里云提供的镜像托管服务

然后有人gcr.io/google-containers 下所有的 Docker 镜像都同步到了中央库

更多的 registry 可以参考这里


2018-03-07 docker , google , cloud , images

使用 rbenv 来管理多个版本 ruby

python 的版本管理有 pyenv,同样的 ruby 也有 rbenv,可以和 pyenv 一样在本地管理多个 ruby 版本,很久没有更新本地的 ruby 版本导致了 jekyll 的依赖不支持才想起来这件事情。

安装

其项目地址为:

https://github.com/rbenv/rbenv

因此安装就很方便

git clone https://github.com/rbenv/rbenv.git ~/.rbenv

然后将下面内容添加到 PATH

export PATH="$HOME/.rbenv/bin:$PATH"

运行

~/.rbenv/bin/rbenv init

根据依赖将输出内容,添加到 ~/.zshrc

重启shell,或者 source ~/.zshrc 使设置生效

再运行检查脚本

curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

一切没问题就OK。

rbenv install 命令不在 rbenv 中,而是在 ruby-build中,可以通过下面的命令安装

apt-get install autoconf bison build-essential libssl-dev libyaml-dev libreadline6 libreadline6-dev zlib1g zlib1g-dev
git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build

如果是 Debian(>=7) 或者 Ubuntu (>=12.10) 也可以使用

sudo apt-get update
sudo apt-get install rbenv ruby-build

使用

rbenv install --list       # 查看可用
rbenv install 2.5.0        # 安装版本

rbenv 中的 Ruby 版本有三个不同的作用域:全局(global),本地(local),当前终端(shell),和 pyenv 类似。

查找版本的优先级是 当前终端 > 本地 > 全局。

rbenv global 2.1.2
rbenv local 2.1.2
rbenv shell 2.1.2

reference


2018-03-04 linux , ruby , versions

每天学习一个命令:du 找出哪个文件夹占用空间

最近 VPS 磁盘空间一直上涨报警,就想查看一下哪个文件夹占用空间比较大,可以腾出一些可用空间来。查了一圈发现 du 命令就是该功能。du 全称 disk usage,

du 文档中就是这么描述他的功能的 —- 文件占用的空间,具体用法

du [OPTIONS] ... [FILE] ...

和绝大多数的命令一样,支持很多的选项,最常用的和 df 命令一样 -h,可以记忆 --human-readable ,用比较人性化的单位,比如 K,M,G。

所以这样就可以使用

du -d 2 -h <dir> | grep '[0-9\,]\+G'
sudo du -h --max-depth=1

来快速的找到占用空间比较大的文件夹,这里的 -d 实际是 --max-depth 的缩写形式,也就是查看 dir 目录下,最多往下查找 2 层,然后以 -h 比较友好的方式输出结果。

用法

除了上面提到了 -h 参数,du 命令还有一些其他的参数

当我们使用 -h 选项时命令会根据不同的大小给出合适的 K, M, G 单位,方便查看,但如果想要强制命令输出统一的单位可以使用 -BM

这里将 -BM 拆开,-B 表示的是 --block-size=SIZE, M 表示的是兆,同理可以使用 -BG 强制使用 G 单位。

查看当前目录及其指定深度目录的大小

du -ah –-max-depth=0
  • -a 显示目录中所有文件及文件夹大小
  • -–max-depth=n 这个选项也能简写成 -d n: 深入到第 n 层目录,此处设置为 0,即表示不深入到子目录,设置为 1,则超过 1 层深度则忽略

忽略目录或文件

du --exclude=/path

只报告目录占用空间总量

-s 显示总和

du -hs /path

如果使用 du -h 那么会打印出 path 下所有目录的占用情况,如果使用 -s 那么只会输出 /path 占用的空间。

额外报告总量

使用 -c 选项会额外在最后打印两行总占用量

du -ch /Download

例如:

..
..
3.3G	Downloads
3.3G	total

分割子目录占用

通常情况下 du 会打印目录及其下所有子目录大小,加入有一个目录 Parent,下方有 SubDirA,SubDirB,还有很多的文件在 Parent 目录下,那么想要知道所有在 Parent 下文件占用,但是不想包括 SubDirA 和 SubDirB 的空间,那么可以使用 -S 选项。

du -h -S -d 2 /path/to/Parent

更多的命令使用方法可以参考 tecmint


2018-03-04 linux , du , df , disk-space , disk

kodi 盒子上的媒体中心?

经常听说 Kodi,却一直没有时间了解一下,最近整理盒子 应用,突然间想起了这个被很多人称为的神器。

什么是 Kodi

Kodi 是一个强大媒体播放软件,支持 Macos, Windows, Linux, iOs 以及 Android 等众多平台,能在各种手机、电脑、平板以及机顶盒中运行。并且因为 Kodi 强大的媒体播放能力,被很多人成为家庭影院必不可少的应用。Kodi 拥有上千种插件,大大扩展了它的功能,丰富的 Kodi 插件为 Kodi 提供了无数的可能。Kodi 的插件种类很多,有音视频源插件,有字幕插件,有影视内容索引插件,有链接搜索助手插件等等。

下载并安装 Kodi

从官网上下载 kodi 写下文章时最新的版本为 17.6,选安卓 64 位

Kodi:https://kodi.tv/

备份地址:

  • http://mirrors.kodi.tv/releases/android/arm64-v8a/kodi-17.6-Krypton-arm64-v8a.apk
  • https://mirror.de.leaseweb.net/xbmc/releases/android/arm64-v8a/kodi-17.6-Krypton-arm64-v8a.apk

比如要在斐讯盒子 T1 中安装 Kodi,首先去盒子的设置 - 高级 - 远程调试打开,如果可能就把盒子的 IP 设置为固定 IP,这个 IP 下面命令中需要使用到。

通过 adb 安装过程如下:

adb connect <ip>
adb shell
adb install /path/to/kodi.apk

设置 Kodi

中文设置

  1. 初次进入 Kodi,选择左侧最上方三个按钮中的第二个图标“system”
  2. 选择“Interface settings”,点击左侧最下方的“Basic”菜单,点 3 次切换到“Expert”模式
  3. 选择“Skin”,更改“Fonts” 为 “Arial based”
  4. 点击左侧 “Regional” 菜单
  5. 更改“Character set” 为 “Chinese Simplified(GBK)”,更改“Language”为“Chinese Simple”
  6. 上述步骤严格按照顺序操作

添加文件安装源

  1. 点击“系统”-“系统设置”,点击左侧最下方的“基础”菜单,点 3 次切换到“专家”模式
  2. 点左侧的“插件”菜单,把“未知来源”项打开
  3. 返回到系统菜单,进入“文件管理”(file manager)
  4. 点击“添加源”,比如输入路径:http://srp.nu, 为源取个名字,更多的源地址,可以看下文
  5. 回 KODI 主页,点击“插件”,点左侧最上方第一个图标“插件浏览器”
  6. 选“从 zip 文件安装”,选择之前设定的名字,安装 zip 文件
  7. 安装完成后,返回上一级,选“从库安装”
  8. 选中需要安装的插件,安装

kodi repo 源

如何安装源这里有两篇详细的图文教程可以参考:

  • https://www.vpnranks.com/kodi-repositories/
  • https://www.vpnranks.com/superrepo-kodi-repository/
  • https://github.com/taxigps/xbmc-addons-chinese/
  • http://archive.org/download/repository.xvbmc
  • https://kodiapps.com/builds-chart
  • https://fusion.tvaddons.co/
  • http://lvtvv.com/repo/
  • http://kodi.emby.media

在安装插件源的时候可能会遇到这几个名词

  • 14.x Helix
  • 15.x Isengard
  • 16.x Jarvis
  • 17.x Krypton

其实这是 Kodi 的版本代号。

总结

其实看到这里就能看到 Kodi 其实绝大部分的功能都是可代替的,流媒体播放固然强大,但是我更喜欢将资源通过 samba 共享出来,在盒子上使用小白播放器,或者在手机上使用 ES Explorer 访问;Kodi 中的直播流功能强烈的依赖于整理好的直播流,而盒子上的超级直播,HDP 直播等等 应用都能很好的代替直播流;再其次 Kodi 带的 DLNA,AirPlay 投屏功能,其实装一个乐播投屏就能完美解决,更不用说我之前的两台海美迪自身就携带 DLNA,局域网投屏本身问题也不大;最后再是 Kodi 的强大的插件库,虽然强大,却无奈经年无人维护,虽然有无数的插件可以使用,却只有寥寥几个可用。所以最后其实 Kodi 也不是居家必备的良药,对于折腾我始终抱有这样的态度,用起来比怎么用要重要的很多,真正用起来才是一个软件应该提供的,我在盒子上话费时间最多的其实就是播放本地局域网共享的视频,其实本身这个功能并不需要那么强大的 Kodi,当然 Kodi 也可以,至于强大的媒体库,对于我这样看完就删,真的只有经典的才保存的玩家来说,太奢侈。


2018-03-03 android , tv , kodi , media

使用Python控制Headless Chrome

首先要解释一下 Headless Chrome,通俗的讲就是运行一个没有GUI的Chrome,在 Headless Chrome 出现以前有 PhantomJS ,但是自从 Headless Chrome 出现之后 PhantomJS 活跃度下降,所以维护者就宣布 了停止继续开发。那么 Headless Chrome 能够什么呢?自动化测试,网页截图,网络调试,爬虫等等任务。Google 说在可预见的未来会一直维护

关于 Headless Chrome 官方有两篇教程

在 Headless Chrome 之前使用 Python 可以结合Chrome推出的 chromedriver 来操作 PhantomJS,那么现在有了Chrome的Headless模式怎么来控制Chrome就是这篇文章要讲的内容。

Chrome DevTools Protocol

在此之前几个重要的网址:

Google官方维护了一份协议叫做 Chrome DevTools Protocol,只要是实现了这一份协议就能够编程来控制Chrome,Chrome 自带的开发者工具其实也是这一份协议的实现

再看几个 Chrome Devtools Protocol 中的概念:

  • Browser 实例可以拥有多个 Page
  • Page 拥有至少一个 frame: main frame, 其他的 frame 可能被网页标记 iframe 和 frame 创建
  • Frame 拥有一个可执行的上下文,在该上下文中 JavaScript 可以被执行,Frame 中也可能有浏览器扩展 extensions 产生的内容被执行

可以用这张 来显示其结构。

常用API

  • Browser 浏览器版本,关闭,命令行等等管理操作
  • Page 该域包含和页面相关接口,页面加载,资源内容,截图,打印等
  • Network 网络请求,Cookie,缓存,证书等内容
  • DOM 文档DOM的获取,修改,删除等
  • Runtime 该域下暴露 JavaScript 相关运行时接口,可以用来执行代码

本地启动 Headless Chrome

本地启动 Headless Chrome 可以参考这篇文章

如果需要使用 Docker 来启动Headless Chrome 也可以使用

docker run -it --rm --name alpine-chrome -p 9222:9222 einverne/alpine-chrome

更加具体的使用介绍可以查看 GitHub

启动之后可以访问: http://localhost:9222/json 来查看是否启动成功。

pychrome

pychrome 是 Chrome Devtools Protocol 的 Python 实现,其他语言的实现,可以查看这个项目。Chrome官方推荐js/nodejs的实现 chrome-remote-interface 还有 node.js 实现的更高层级的 Puppeteer

这里主要摘录几个常用的操作

截图

使用 Page.captureScreenshot

def screenshot(browser, url, filename='image.png'):
    tab = browser.new_tab()
    tab.start()
    tab.call_method('Page.navigate', url=url, _timeout=5)
    tab.wait(10)
    # 截取当前Tab屏幕,结果为图片内容Base64
    screen_base64 = tab.call_method("Page.captureScreenshot")
    image_data = screen_base64.get('data', '')
    with open(filename, 'wb') as f:
        f.write(image_data.decode('base64'))
    tab.stop()
    browser.close_tab(tab) 

打印PDF

同样使用 Page 下方法

def print_to_pdf(browser, url, filename='file.pdf'):
    tab = browser.new_tab()
    tab.start()
    tab.call_method('Page.navigate', url=url, _timeout=5)
    tab.wait(10)
    pdf_data = tab.call_method('Page.printToPDF', landscape=False).get('data')
    with open(filename, 'wb') as f:
        f.write(pdf_data.decode('base64'))
    tab.stop()
    browser.close_tab(tab)

向下翻页

def perform_input(browser):
    """向下浏览页面"""
    tab = browser.new_tab()
    tab.start()
    tab.Page.enable()
    tab.Page.navigate(url='https://www.douban.com/', _timeout=5)
    tab.wait(5)
    # 更多 keycode 可以参考 https://msdn.microsoft.com/en-us/library/dd375731(VS.85).aspx
    keycode = int(0x22)
    tab.Input.dispatchKeyEvent(type='keyDown', windowsVirtualKeyCode=keycode, nativeVirtualKeyCode=keycode)
    tab.wait(3)
    screen_base64 = tab.call_method("Page.captureScreenshot")
    image_data = screen_base64.get('data', '')
    with open("keyDown.png", 'wb') as f:
        f.write(image_data.decode('base64'))
    tab.stop()
    browser.close_tab(tab)

获取网页HTML

获取动态网页的网页内容,等待JS执行完成后获取HTML

def get_html(browser, url):
    tab = browser.new_tab()
    tab.start()
    tab.call_method('Page.navigate', url=url, _timeout=5)
    tab.wait(10)
    html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
    tab.stop()
    browser.close_tab(tab)
    return html['result']['value']

操纵网页元素注入JS

下面这段代码是访问 baidu,输入关键字,并点击搜索按钮,解析搜索结果并打印

def perform_click(browser):
    tab = browser.new_tab()

    # def loading_finished(**kwargs):
    #     print "[loading finished]"
    #
    # # when HTTP request has finished loading
    # tab.set_listener("Network.loadingFinished", loading_finished)

    tab.start()

    # call method
    # tab.Network.enable()
    tab.Network.enable()
    tab.Page.enable()
    tab.Runtime.enable()

    def dom_content_event_fired(**kwargs):
        print "[content] dom content event fired"
        tab.DOM.enable()
        root = tab.DOM.getDocument()
        root_node_id = root.get('root', {}).get('nodeId', '')
        # 找到输入框
        input_box = tab.DOM.querySelector(nodeId=root_node_id, selector='#kw')
        # tab.DOM.setNodeValue(nodeId=input_box, value='hello')
        tab.Runtime.evaluate(expression='document.getElementById("kw").value="Chrome"', )
        # 找到搜索按钮
        search_btn = tab.DOM.querySelector(nodeId=root_node_id, selector='#su')
        remote_node = tab.DOM.resolveNode(nodeId=search_btn.get('nodeId', ''))
        # 执行点击
        tab.Runtime.callFunctionOn(functionDeclaration='(function() { this.click(); })',
                                   objectId=remote_node.get('object', {}).get('objectId', {}))
        tab.wait(3)
        # 输出结果
        html = tab.Runtime.evaluate(expression="document.documentElement.outerHTML")
        html_value = html.get('result', {}).get('value', '').encode('utf-8')
        soup = BeautifulSoup(html_value, 'html.parser')
        l = soup.select('h3 > a')
        for result in l:
            print result.text
            print result['href']

        screen_base64 = tab.call_method("Page.captureScreenshot")
        image_data = screen_base64.get('data', '')
        with open("test.png", 'wb') as f:
            f.write(image_data.decode('base64'))

        # tab.DOM.performSearch(query='xpath', includeUserAgentShadowDOM=True)
        # stop the tab (stop handle events and stop recv message from chrome)
        tab.stop()

        # close tab
        browser.close_tab(tab)

    tab.set_listener("Page.domContentEventFired", dom_content_event_fired)

    # tab.call_method("Page.reload", ignoreCache=False)
    tab.call_method("Page.navigate", url='https://www.baidu.com', _timeout=5)
    tab.wait(20)

完整的代码可以参考 GitHub

其他可学习的网站

这篇文章讲述了使用 nodejs 的库 NickJS来爬取网站的要点。

这篇使用 nodejs 的chrome-remote-interface,来抓取网页。

其他

  • https://stackoverflow.com/questions/28430479/using-google-chrome-remote-debugging-protocol
  • https://blog.phantombuster.com/web-scraping-in-2017-headless-chrome-tips-tricks-4d6521d695e8
  • https://stackoverflow.com/questions/7848878/how-to-use-google-chrome-remote-debugging-protocol-in-http
  • https://github.com/auchenberg/devtools-remote
  • https://github.com/auchenberg/chrome-devtools-app
  • https://intoli.com/blog/running-selenium-with-headless-chrome
  • https://duo.com/blog/driving-headless-chrome-with-python

2018-03-01 chrome , headless-chrome , linux , python

电子书

最近文章

  • Linux 下使用 emoji Ubuntu 或者其他一些 Linux 发行版会内置 Google Noto Color emoji font
  • 理解 GraphQL Schema 结构定义 在上一篇 GraphQL 使用 中了解了 GraphQL 大致用法,如果要更加深入的了解 GraphQL ,那就不得不重新从 Schema 来认识 GraphQL,说到底 GraphQL 还是一个强类型定义,客户端可操作的类型都是需要提前定义好的,这个结构就是这篇文章的重点 Schema.
  • 从 MySQL 迁移到 PostgreSQL 方案调研 之前的文章 PostgreSQL 初识 和 PostgreSQL 数据类型 大致的把 PostgreSQL 了解了一下,那么接下来就是真正地把它用起来。
  • GraphQL 使用 很多人都知道 GraphQL 起源于 Facebook,但是似乎很少中文材料提到 GraphQL 出现的契机,我在看完这个纪录片 之后才对 GraphQL 的出现有更加深刻的了解。都知道当年 Facebook 的移动客户端都是网页,随着移动互联网发展,Facebook 网页实现的客户端性能和体验受到非常严重的影响,所以后来不得不去做原生的应用。那么这个时候就遇到了一个问题,原来直接使用网页,那么不同客户端用的接口都是给网页用的,最多做一下屏幕的适配,但是如果使用原生的应用,那么必然会需要设计不同的 API,而 Facebook 的工程师发现,对于复杂的 Feed 流,评论等等,用 RESTful 接口将是一个灾难,所以一帮人开始设计一种查询语言,这就是后来的 GraphQL。也应该纠正一下 Facebook 那帮工程师,只是开源了一份设计和一份实现,但他们万万没有想到开源社区的力量,就像片中 Nick Schrock 说的那样,他低估了社区的力量,在短短的几个月,几年时间中,GraphQL 就已经有了非常多语言的支持,周边应用也非常丰富。原来在脑海里的想法,都没有到生产环境中用过,就这样在所有社区的人的努力下成为了改变这个行业的一部分。
  • Drools : Guided Decision Table Column