[TOC]

1. 前言

在web开发,api开发过程中,我们需要访问本地开发环境,测试环境,准生产环境,线上环境
为了使用同一个域名去访问不同环境下的服务器,通常有2种方式
1. dns解析方式,较多时候使用hosts的方式进行dns解析
2. proxy代理方式

wwek经常使用SwitchHosts,同时推荐wproxy和ZanProxy
下面是相关常见工具推荐

2. Proxy代理类

2.1 wproxy

wproxy官网
wproxy开源
HTTP, HTTPS, WebSocket debugging proxy
whistle(读音[ˈwɪsəl],拼音[wēisǒu])是基于Node实现的跨平台抓包调试代理工具,有以下基本功能:

查看HTTP、HTTPS请求响应内容
查看WebSocket、Socket收发的帧数据
设置请求hosts、上游http/socks代理
修改请求url、方法、头部、内容
修改响应状态码、头部、内容,并支持本地替换
修改WebSocket或Socket收发的帧数据
内置调试移动端页面的weinre和log
作为HTTP代理或反向代理
支持用Node编写插件扩展功能
img

2.2 ZanProxy

ZanProxy官网
ZanProxy开源
An extensible proxy for PC/Mobile/APP developer
Zan Proxy是一个用Node.js编写的HTTP代理服务器,可用于修改请求地址和模拟响应数据。
它同时也是一个自定义DNS解析和请求监控的工具。 该代理服务器有人性化的界面,简单易用。我们还为开发者提供了高级插件机制来自定义代理行为。

主要特性
支持HTTP,HTTPS和Websocket代理
支持自定义请求转发,可转发到本地文件
支持远程规则,可以在项目内共享规则
支持mock响应数据
支持自定义DNS解析
支持自定义插件,可定制代理行为
图形化配置,上手容易
img

2.3 anyproxy

anyproxy官网
anyproxy开源
A fully configurable http/https proxy in NodeJS
AnyProxy是一个基于NodeJS的,可供插件配置的HTTP/HTTPS代理服务器。
img

2.4 Charles

Charles官网
Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet. This includes requests, responses and the HTTP headers (which contain the cookies and caching information).
Charles是一个HTTP代理/ HTTP监视器/反向代理,使开发人员能够查看其机器和Internet之间的所有HTTP和SSL / HTTPS流量。 这包括请求,响应和HTTP标头(包含cookie和缓存信息)。
OSX上的http/https抓包工具,算是人人皆知,单机使用非常好用
img

2.5 Telerik Fiddler

Fiddler官网
The free web debugging proxy for any browser, system or platform
Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件)。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。
老牌Windows上的http/https抓包工具,算是人人皆知,单机使用非常好用

2.6 mitmproxy

mitmproxy官网
mitmproxy开源
mitmproxy is a free and open source interactive HTTPS proxy.
具有交互式TLS功能的拦截HTTP代理,适用于渗透测试人员和软件开发人员。
img

3. Hosts类

3.1 SwitchHosts

SwitchHosts官网
SwitchHosts开源
这是一个用于快速切换 hosts 文件的小程序,基于 Electron 开发,同时使用了 React、Ant Design 以及 CodeMirror 等框架/库。
功能特性
快速切换 hosts
hosts 文件语法高亮
在线 hosts 方案
系统托盘图标快速切换
macOS: 支持 Alfred workflow 快速切换
img

4. Proxy辅助

Proxifier

Proxifier官网
Proxifier是一个跨平台的设置代理客户端,支持socks和http代理协议,它可以拦截任意客户端的所有网络请求,并把请求代理到指定的代理服务器。
用在一些不支持设置代理的客户端软件就特别适用
Proxifier官方只有Windows和Mac版

什么书讲的什么

《设计数据密集型应用》 又名《数据密集型应用系统设计》

乍一看本书是教你写后端应用呢,实际的内容比这个高到哪里去了 

当我们在设计后端应用的时候,围绕的往往根本还是数据处理、存储、展示,那么这“数据大陆”的全貌是什么呢?


第一部分

在单机数据库中数据库系统会面临哪些挑战

数据库有哪些模型,有哪些查询方式

底层的存储结构是什么,如何高效的检索数据

编码格式

第二部分

在分布式数据库中会涉及的基本概念

分布式系统中会有哪些麻烦事,如何化解

一致性和共识分布式系统中的核心


第三部分

在大数据运用中实事数据(业务表亦或事实表)数据是如何衍生的

批处理

流处理

以及“数据大陆”未来发展

最后作者还在数据隐私保护方面做了一些讨论


本书抽丝剥茧,高屋建瓴。从问题的根本开始,掌握这些“源头”可以让架构选型和设计做的更好

比较难能可贵的是本书不是一堆各种术语堆砌,而是循序渐的让你从麻烦到破解,从而引导你“攻破”麻烦,然后你就自然而然的能看懂“术语”了。

看看网友的评价
本书为数据系统的设计、实现、与评价提供了很好的概念框架。读完并理解本书内容后,读者可以轻松看破大多数的技术忽悠,与技术砖家撕起来虎虎生风🤣。 —— Vonng
提纲挈领  —— 介错
我想给这本书打一百颗星。(截取) ——  brokilon    


那么哪里可以看呢
你可以在线免费阅读: https://github.com/Vonng/ddia/ (非官方的中文翻译版)要买纸值的看豆瓣:https://book.douban.com/subject/30329536/ (原作者授权中文翻译出版)

使用工具来检查项目代码对PHP7的兼容情况

使用 PHP_CodeSniffer + PHPCompatibility
PHP_CodeSniffer
PHP_CodeSniffer对PHP,JavaScript和CSS文件进行标记,并检测违反已定义的一组编码标准的行为。
https://github.com/squizlabs/PHP_CodeSniffer

PHPCompatibility
PHP兼容性检查
https://github.com/PHPCompatibility/PHPCompatibility
更多PHPCompatibility相关内容,请参考 https://www.sitepoint.com/quick-intro-phpcompatibility-standard-for-phpcs-are-you-php7-ready/

安装和运行

按照官方文档进行安装
先安装PHP_CodeSniffer
再安装插件PHPCompatibility

composer global require “squizlabs/php_codesniffer=*”
phpcs –config-set installed_paths /data/wwwroot/php/PHPCompatibility
phpcs –config-set installed_paths “D:\projects\php\PHPCompatibility”

查看是否安装上
phpcs -i
#The installed coding standards are MySource, PEAR, PSR1, PSR12, PSR2, Squiz, Zend and PHPCompatibility
执行检查
phpcs –standard=PHPCompatibility –runtime-set testVersion 7.0 /data/wwwroot/project/ >phpcs.log
#执行的过程会有些慢,慢慢等吧

根据phpcs.log中的日志,解决error级别的错误

FILE: \data\wwwroot\project\Common\function.php

FOUND 3 ERRORS AFFECTING 2 LINES

9 | ERROR | Function split() is deprecated since PHP 5.3 and removed since PHP 7.0; Use preg_split() instead
11 | ERROR | Function eregi() is deprecated since PHP 5.3 and removed since PHP 7.0; Use preg_match() instead

11 | ERROR | Extension ‘ereg’ is deprecated since PHP 5.3 and removed since PHP 7.0; Use pcre instead

再加上手动按照PHP版本逐级检查不兼容

PHP5.4.x 》 PHP5.6.x

从 PHP 5.4.x 迁移到 PHP 5.5.x

不向后兼容的变更的检查
检查 pack() 和 unpack() pass

PHP 5.5.x 中废弃的特性的检查
preg_replace() 函数中用到的 /e 修饰符现在被弃用。可以使用 preg_replace_callback() 函数来替代。
mcrypt 的相关函数

PHP 5.5.x 》 PHP5.6.x

从PHP 5.5.x 移植到 PHP 5.6.x
不向后兼容的变更的检查
严格的 json_decode()

PHP 5.6.x 》 PHP7.0.x

从PHP 5.6.x 移植到 PHP 7.0.x
不向后兼容的变更的检查
ereg相关函数
split()

测试

把主要业务功能都过一遍测试

升级部署问题

方式1:容器部署:php-fpm改用docker的方式部署,这样比较方便多版本切换和回滚
方式2:传统部署:部署新服,业务流量切过去,原有服暂时不要销毁,等业务没问题后再慢慢销毁原有服, 如果是云平台应该为所有将要销毁的系统做一个快照以备不时之需

本文提供
Windows系统下使用laradock作为开发运行环境, PhpStorm作为开发IDE, 如何配置xdebug 断点调试
OSX系统下使用laradock作为开发运行环境, PhpStorm作为开发IDE, 如何配置xdebug 断点调试

laradock中php-fpm 的xdebug.ini配置

laradock/php-fpm/xdebug.ini
如果是Windows系统则改为

xdebug.remote_host=docker.for.win.localhost
xdebug.remote_connect_back=0

如果是OSX系统则改为

xdebug.remote_host=docker.for.mac.localhost
xdebug.remote_connect_back=0

xdebug.ini文件中其他参数不用动
xdebug.remote_host参数设置的是xdebug服务器的地址,这里实际上是“母鸡”也就是phpstorm的网络地址
xdebug.remote_connect_back这个参数如果为1表示根据请求来源“remote_host”,来发起调试,在docker环境下有网络nat所以不会成功,这个参数的改为0

phpstorm的配置

设置中> Languages & Frameworks > PHP
Debug 默认参数可以不动
Servers 中Name: laradock Host:你的网址 Port:80 Debugger:Xdebug
勾选 “Use path mappings” 把项目目录和laradock中 /var/www/你的项目 进行目录映射
phpstorm的xdebug配置不在累述,可以参见laradock http://laradock.io/documentation/#install-xdebug
laradock + phpstorm配置xdebug不成功多半是xdebug.remote_connect_back没设置为0

如何在一个Docker中运行多个程序进程?

我们都知道Docker容器的哲学是一个Docker容器只运行一个进程,但是有时候我们就是需要在一个Docker容器中运行多个进程
那么基本思路是在Dockerfile 的CMD 或者 ENTRYPOINT 运行一个”东西”,然后再让这个”东西”运行多个其他进程
简单说来是用Bash Shell脚本或者三方进程守护 (Monit,Skaware S6,Supervisor),其他没讲到的三方进程守护工具同理

Bash Shell脚本

入口文件运行一个Bash Shell 脚本, 然后在这个脚本内去拉起多个进程
注意最后要增加一个死循环不要让这个脚本退出,否则拉起的进程也退出了
run.sh

#!/bin/bash

# start 1
start1  > /var/log/start1.log 2>&1 &
# start 2
start2 > /var/log/start2.log 2>&1 &

# just keep this script running
while [[ true ]]; do
    sleep 1
done

在Dockerfile的入口中运行run.sh

ENTRYPOINT  ["run.sh"]

用Bash Shell 的方式,任意发行版的linux都支持,缺点是不能拉起crash的进程,也就是只能拉起运行不能”守护”
如果不关心进程crash问题那么可以用这种方式

三方容器进程初始化之-dumb-init

dumb-init官方
A minimal init system for Linux containers
一个最小化的Linux容器初始化系统
dumb-init是一个简单的进程监控器和init系统,设计为在最小容器环境(如Docker)中作为PID 1运行。它被部署为一个用C编写的小型静态链接二进制文件。
Dockerfile 参考

# Runs "/usr/bin/dumb-init -- /my/script --with --args"
ENTRYPOINT ["/usr/bin/dumb-init", "--"]

# or if you use --rewrite or other cli flags
# ENTRYPOINT ["dumb-init", "--rewrite", "2:3", "--"]

CMD ["/my/script", "--with", "--args"]

ServiceMesh的数据平面Envoy Proxy的容器镜像就是使用的dumb-init

三方容器进程初始化之-tini

tini官方
A tiny but valid init for containers
容器的一个小而有效的init

三方进程守护之-Monit

Monit和Supervisor还是有很大区别的,Supervisor管理的都是前台执行的进程,Monit既可以管理前台进程也可以管理后台进程,简单的说,在CentOS中使用service xxx start 启动的程序,使用Monit可以直接管理,这就解决了很多没有前台方式启动的程序不能用Supervisor来管理的问题。
Dockerfile 参考

ENTRYPOINT ["/usr/bin/monit","-I"]

三方进程守护之-Supervisor

大名鼎鼎的 Supervisor
如果有多种版本的linux要用那么可以用Supervisor做统一进程守护管理,网上资料一大堆
注意要以前台程序运行,配置文件中要有,如果是后台的方式docker会退出

[supervisord]
nodaemon=true

Dockerfile 参考

ENTRYPOINT ["supervisord", "-c", "/etc/supervisor/conf.d/youconf.conf"]

三方进程守护之-Skaware S6

Supervisor是常见的进程守护程序,不过程序文件太大,想要容器镜像尽量小,在特别是用Alpine作为基础镜像的时候推荐使用Skaware S6
参考这个微服务基础镜像 https://github.com/nicholasjackson/microservice-basebox 他就是用 Skaware 作为进程守护程序运行多个进程的
如果基础容器镜像是本身就是Alpine,那就再合适不过了
Dockerfile 参考

# skaware s6 daemon runner
RUN mkdir /s6 \
 && wget --no-check-certificate https://github.com/just-containers/skaware/releases/download/v1.21.2/s6-2.6.1.1-linux-amd64-bin.tar.gz \
 && tar -xvzf s6-2.6.1.1-linux-amd64-bin.tar.gz --directory /s6 --strip-components=1 \
 && mv /s6/bin/* /usr/bin \
 && rm -rf s6-2.6.1.1-linux-amd64-bin.tar.gz \
 && rm -rf /s6 \
 && cd /usr/bin/ \
 && chmod -R 755 s6-accessrules-cdb-from-fs s6-accessrules-fs-from-cdb \
    s6-applyuidgid s6-cleanfifodir s6-connlimit s6-envdir s6-envuidgid \
    s6-fdholder-daemon s6-fdholder-delete s6-fdholder-deletec s6-fdholder-getdump \
    s6-fdholder-getdumpc s6-fdholder-list s6-fdholder-listc s6-fdholder-retrieve \
    s6-fdholder-retrievec s6-fdholder-setdump s6-fdholder-setdumpc s6-fdholder-store \
    s6-fdholder-storec s6-fdholder-transferdump s6-fdholder-transferdumpc s6-fdholderd \
    s6-fghack s6-ftrig-listen s6-ftrig-listen1 s6-ftrig-notify s6-ftrig-wait s6-ftrigrd \
    s6-ioconnect s6-ipcclient s6-ipcserver s6-ipcserver-access s6-ipcserver-socketbinder \
    s6-ipcserverd s6-log s6-mkfifodir s6-notifyoncheck s6-setlock s6-setsid s6-setuidgid \
    s6-softlimit s6-sudo s6-sudoc s6-sudod s6-supervise s6-svc s6-svlisten s6-svlisten1 \
    s6-svok s6-svscan s6-svscanctl s6-svstat s6-svwait s6-tai64n s6-tai64nlocal s6lockd ucspilogd \
 && cd -

# 预处理s6配置文件
RUN mkdir -p /etc/s6/.s6-svscan  \
 && ln -s /bin/true /etc/s6/.s6-svscan/finish  \
 && mkdir -p /etc/s6/cron \
 && mkdir -p /etc/s6/app \
 && ln -s /bin/true /etc/s6/cron/finish \
 && ln -s /bin/true /etc/s6/app/finish

# corotab 文件内容
ADD cronfile /var/spool/cron/root
# 运行Bash 脚本
ADD cron.run /etc/s6/cron/run
ADD app.run /etc/s6/app/run

ENTRYPOINT ["/usr/bin/s6-svscan","/etc/s6"]

cron.run example

#!/usr/bin/env bash
exec crond -n

app.run example

#!/usr/bin/env bash
exec app

三方进程守护之-s6-overlay

s6-overlay 是基于 Skaware S6适用于容器的进程守护工具
s6-overlay 官网 https://github.com/just-containers/s6-overlay
Dockerfile 参考

# Install s6-overlay 进程守护管理.
ENV S6_VERSION v1.21.4.0
RUN curl -L "https://github.com/just-containers/s6-overlay/releases/download/$S6_VERSION/s6-overlay-amd64.tar.gz" > /tmp/s6-overlay-amd64.tar.gz
RUN tar xzf /tmp/s6-overlay-amd64.tar.gz -C / --exclude="./bin" && \
    tar xzf /tmp/s6-overlay-amd64.tar.gz -C /usr ./bin \
    && rm /tmp/s6-overlay-amd64.tar.gz
ENV S6_BEHAVIOUR_IF_STAGE2_FAILS 1

# Set up a standard volume for logs.
VOLUME ["/var/log/services"]


# 设置入口为 s6-based init.
ENTRYPOINT ["/init"]

三方进程守护之-runit

runit官网http://smarden.org/runit/
具体的使用方法见官网
在Docker生态圈, phusion/baseimage-docker, gitlab 在使用runit作为进程管理工具

下面以要运行cron 和 ssh 为例
/etc/service/ 为配置文件目录

/etc/service/sshd 为要运行的程序目录
/etc/service/sshd/run 为需要运行的程序入口脚本文件
cat run

#!/bin/sh
set -e
exec /usr/sbin/sshd -D

/etc/service/cron 为要运行的程序目录
/etc/service/cron/run 为需要运行的程序入口脚本文件
cat run

#!/bin/sh
exec /usr/sbin/cron -f

Dockerfile 参考

ENTRYPOINT ["/usr/bin/runsvdir","-P","/etc/service"]

三方进程守护之-Systemd

在 docker 中使用 Systemd 需要在 docker run 的时候开启特权模式 –privileged ,所以不推荐
这个直接放弃了
Dockerfile 参考

ENTRYPOINT ["/usr/sbin/init"]

参考资料

Alpine里的go应用,你猜他能有多小? http://blog.csdn.net/sisiy2015/article/details/50350261
如何运行多进程Docker容器? http://dockone.io/article/951
在Docker Container中启动定时任务 http://dockone.io/article/1070
Docker容器内多进程管理(一)-Supervisor http://www.linuxprobe.com/docker-process-management1.html
Docker容器内多进程管理(二)-Monit http://www.linuxprobe.com/docker-process-management2.html
关于S6和Runit的论坛讨论 S6 or Runit, not systemd https://www.linuxquestions.org/questions/slackware-14/s6-or-runit-not-systemd-4175465428/
[译] runit 快速入门 https://segmentfault.com/a/1190000006644578

需求来源,需要用PHP解析HTML提取我想要的数据

用PHP写网站爬虫的时候,需要把爬取的网页进行解析,提取里面想要的数据,这个过程叫做网页HTML中数据结构化。
很多人应该知道用phpQuery像JQuery一样的语法进行网页处理,抽取想要的数据。
但是在复杂一些的场景phpQuery并不能很好的完成工作,说简单点就是复杂场景不好用。
有没有更好的方式呢,我们看看商业爬虫软件是怎么做的。

他山之石,商业爬虫怎么做的

市面上商业爬虫怎么做HTML解析抽取数据的

看看市面上商业爬虫怎么做HTML解析结构化数据字段抽取
通过观察市面上商业爬虫工具GooSeeker、神箭手、八爪鱼,可以知道他们都用一个叫做XPath表达式的方式提取需要的字段数据。
那么可以判断使用XPath对HTML解析进行数据定位提取就是通行最佳的方式之一。

PHP中的XPath支持

XPath表达式可以查找HTML节点或元素,是一种路径表达语言。
那么需要先学习下XPath的基础,花个1-2小时入门,XPath就是页面数据提取能力的最佳内功之一,这个时间值得花。
既然用XPath提取页面数据是通行的方式,那么PHP中支持XPath的扩展包是什么呢?
为了帮大家节约时间,Symfony DomCrawler 就是PHP中最佳XPath包之一,直接用他吧,Symfony出品质量可是有目共睹,PHP热门框架laravel都用Symfony的包。
Symfony DomCrawler官方文档介绍的实例有限,建议有需要的情况下把代码读下,更能熟知新用法。

撸起袖子干,用DomCrawler做XPath HTML页面解析结构化数据抽取

基本思路

在Chrome浏览器中安装”XPath Helper”插件(XPath Helper怎么使用见参考资料)
打开需要解析的网站页面编写和测试XPath表达式
在PHP代码用DomCrawler使用上XPath表达式抽取想要的字段数据

实例演示

解析《神偷奶爸3》页面的的电影介绍信息
在Chrome中用编写测试XPath表达式

xpath helper使用


在项目下用composer安装guzzlehttp/guzzle(http client)、symfony/dom-crawler(Symfony DomCrawler)

composer require  guzzlehttp/guzzle
composer require  symfony/dom-crawler

下面直接上代码,在代码中用注释做说明
php Douban.php

<?php
/**
 * Created by PhpStorm.
 * User: wwek
 * Date: 2017/7/9
 * Time: 21:41
 */

require __DIR__ . '/vendor/autoload.php';

use GuzzleHttp\Client;
use Symfony\Component\DomCrawler\Crawler;

print_r(json_encode(Spider(), JSON_UNESCAPED_UNICODE));
//print_r(Spider());

function Spider()
{
    //需要爬取的页面
    $url = 'https://movie.douban.com/subject/25812712/?from=showing';

    //下载网页内容
    $client   = new Client([
        'timeout' => 10,
        'headers' => ['User-Agent' => 'Mozilla/5.0 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)',
        ],
    ]);
    $response = $client->request('GET', $url)->getBody()->getContents();

    //进行XPath页面数据抽取
    $data    = []; //结构化数据存本数组
    $crawler = new Crawler();
    $crawler->addHtmlContent($response);

    try {
        //电影名称
        //网页结构中用css选择器用id的比较容易写xpath表达式
        $data['name'] = $crawler->filterXPath('//*[@id="content"]/h1/span[1]')->text();
        //电影海报
        $data['cover'] = $crawler->filterXPath('//*[@id="mainpic"]/a/img/@src')->text();
        //导演
        $data['director'] = $crawler->filterXPath('//*[@id="info"]/span[1]/span[2]')->text();
        //多个导演处理成数组
        $data['director'] = explode('/', $data['director']);
        //过滤前后空格
        $data['director'] = array_map('trim', $data['director']);

        //编剧
        $data['cover'] = $crawler->filterXPath('//*[@id="info"]/span[2]/span[2]/a')->text();
        //主演
        $data['mactor'] = $crawler->filterXPath('//*[@id="info"]/span[contains(@class,"actor")]/span[contains(@class,"attrs")]')->text();
        //多个主演处理成数组
        $data['mactor'] = explode('/', $data['mactor']);
        //过滤前后空格
        $data['mactor'] = array_map('trim', $data['mactor']);

        //上映日期
        $data['rdate'] = $crawler->filterXPath('//*[@id="info"]')->text();
        //使用正则进行抽取
        preg_match_all("/(\d{4})-(\d{2})-(\d{2})\(.*?\)/", $data['rdate'], $rdate); //2017-07-07(中国大陆) / 2017-06-14(安锡动画电影节) / 2017-06-30(美国)
        $data['rdate'] = $rdate[0];
        //简介
        //演示使用class选择器的方式
        $data['introduction'] = trim($crawler->filterXPath('//div[contains(@class,"indent")]/span')->text());

        //演员
        //本xpath表达式会得到多个对象结果,用each方法进行遍历
        //each是传入的参数是一个闭包,在闭包中使用外部的变量使用use方法,并使用变量指针
        $crawler->filterXPath('//ul[contains(@class,"celebrities-list from-subject")]/li')->each(function (Crawler $node, $i) use (&$data) {
            $actor['name']   = $node->filterXPath('//div[contains(@class,"info")]/span[contains(@class,"name")]/a')->text(); //名字
            $actor['role']   = $node->filterXPath('//div[contains(@class,"info")]/span[contains(@class,"role")]')->text(); //角色
            $actor['avatar'] = $node->filterXPath('//a/div[contains(@class,"avatar")]/@style')->text(); //头像
            //background-image: url(https://img3.doubanio.com/img/celebrity/medium/5253.jpg) 正则抽取头像图片
            preg_match_all("/((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+\.(jpg|jpeg|gif|png)/", $actor['avatar'], $avatar);
            $actor['avatar'] = $avatar[0][0];
            //print_r($actor);
            $data['actor'][] = $actor;
        });

    } catch (\Exception $e) {

    }

    return $data;

}

执行结果

{
    "name": "神偷奶爸3 Despicable Me 3",
    "cover": "https://img3.doubanio.com/view/movie_poster_cover/lpst/public/p2469070974.webp",
    "director": [
        "凯尔·巴尔达",
        "皮艾尔·柯芬"
    ],
    "mactor": [
        "史蒂夫·卡瑞尔",
        "克里斯汀·韦格",
        "崔·帕克",
        "米兰达·卡斯格拉夫",
        "拉塞尔·布兰德",
        "迈克尔·贝亚蒂",
        "达纳·盖尔",
        "皮艾尔·柯芬",
        "安迪·尼曼"
    ],
    "rdate": [
        "2017-07-07(中国大陆)",
        "2017-06-14(安锡动画电影节)",
        "2017-06-30(美国)"
    ],
    "introduction": "  《神偷奶爸3》将延续前两部的温馨、搞笑风格,聚焦格鲁和露西的婚后生活,继续讲述格鲁和三个女儿的爆笑故事。“恶棍”奶爸格鲁将会如何对付大反派巴萨扎·布莱德,调皮可爱的小黄人们又会如何耍贱卖萌,无疑让全球观众万分期待。该片配音也最大程度沿用前作阵容,史蒂夫·卡瑞尔继续为男主角格鲁配音,皮埃尔·柯芬也将继续为经典角色小黄人配音,而新角色巴萨扎·布莱德则由《南方公园》主创元老崔·帕克为其配音。",
    "actor": [
        {
            "name": "皮艾尔·柯芬 ",
            "role": "导演",
            "avatar": "https://img3.doubanio.com/img/celebrity/medium/1389806916.36.jpg"
        },
        {
            "name": "凯尔·巴尔达 ",
            "role": "导演",
            "avatar": "https://img3.doubanio.com/img/celebrity/medium/51602.jpg"
        },
        {
            "name": "史蒂夫·卡瑞尔 ",
            "role": "饰 Gru / Dru",
            "avatar": "https://img3.doubanio.com/img/celebrity/medium/15731.jpg"
        },
        {
            "name": "克里斯汀·韦格 ",
            "role": "饰 Lucy Wilde",
            "avatar": "https://img3.doubanio.com/img/celebrity/medium/24543.jpg"
        },
        {
            "name": "崔·帕克 ",
            "role": "饰 Balthazar Bratt",
            "avatar": "https://img3.doubanio.com/img/celebrity/medium/5253.jpg"
        },
        {
            "name": "米兰达·卡斯格拉夫 ",
            "role": "饰 Margo",
            "avatar": "https://img1.doubanio.com/img/celebrity/medium/1410165824.37.jpg"
        }
    ]
}

经验

网页上数据是动态变化的,没获取到的字段需要用try catch进行异常处理,这样程序就不会崩溃
XPath表达式孰能生巧基本能应付绝大多数的需求
某些数据如果用XPath表达式也不好取,或者取出来的数据还需要加工的,用正则表达式处理,用preg_match_all进行抽取,用preg_replace进行替换
用strip_tags()函数去除HTML、XML以及PHP的标签,加参数可以保留标签去除,如处理文章内容strip_tags($str, "<p><img><strong>"),后留后面参数中的标签
一些常用的正则表达式


$str=preg_replace("/\s+/", " ", $str); //过滤多余回车 $str=preg_replace("/<[ ]+/si","<",$str); //过滤<__("<"号后面带空格) $str=preg_replace("/<\!--.*?-->/si","",$str); //注释 $str=preg_replace("/<(\!.*?)>/si","",$str); //过滤DOCTYPE $str=preg_replace("/<(\/?html.*?)>/si","",$str); //过滤html标签 $str=preg_replace("/<(\/?head.*?)>/si","",$str); //过滤head标签 $str=preg_replace("/<(\/?meta.*?)>/si","",$str); //过滤meta标签 $str=preg_replace("/<(\/?body.*?)>/si","",$str); //过滤body标签 $str=preg_replace("/<(\/?link.*?)>/si","",$str); //过滤link标签 $str=preg_replace("/<(\/?form.*?)>/si","",$str); //过滤form标签 $str=preg_replace("/cookie/si","COOKIE",$str); //过滤COOKIE标签 $str=preg_replace("/<(applet.*?)>(.*?)<(\/applet.*?)>/si","",$str); //过滤applet标签 $str=preg_replace("/<(\/?applet.*?)>/si","",$str); //过滤applet标签 $str=preg_replace("/<(style.*?)>(.*?)<(\/style.*?)>/si","",$str); //过滤style标签 $str=preg_replace("/<(\/?style.*?)>/si","",$str); //过滤style标签 $str=preg_replace("/<(title.*?)>(.*?)<(\/title.*?)>/si","",$str); //过滤title标签 $str=preg_replace("/<(\/?title.*?)>/si","",$str); //过滤title标签 $str=preg_replace("/<(object.*?)>(.*?)<(\/object.*?)>/si","",$str); //过滤object标签 $str=preg_replace("/<(\/?objec.*?)>/si","",$str); //过滤object标签 $str=preg_replace("/<(noframes.*?)>(.*?)<(\/noframes.*?)>/si","",$str); //过滤noframes标签 $str=preg_replace("/<(\/?noframes.*?)>/si","",$str); //过滤noframes标签 $str=preg_replace("/<(i?frame.*?)>(.*?)<(\/i?frame.*?)>/si","",$str); //过滤frame标签 $str=preg_replace("/<(\/?i?frame.*?)>/si","",$str); //过滤frame标签 $str=preg_replace("/<(script.*?)>(.*?)<(\/script.*?)>/si","",$str); //过滤script标签 $str=preg_replace("/<(\/?script.*?)>/si","",$str); //过滤script标签 $str=preg_replace("/javascript/si","Javascript",$str); //过滤script标签 $str=preg_replace("/vbscript/si","Vbscript",$str); //过滤script标签 $str=preg_replace("/on([a-z]+)\s*=/si","On\\1=",$str); //过滤script标签 $str=preg_replace("/&#/si","&#",$str); //过滤script标签,如javAsCript:alert(

参考资料

官方文档The DomCrawler Component
八爪鱼的了解XPath常用术语和表达式解析 十分钟轻松入门
gooseeker的xpath基础知识培训
神箭手的常用的辅助开发工具
XPath Helper:chrome爬虫网页解析工具 Chrome插件图文教程
在PHP中,您如何解析和处理HTML/XML?

扩展阅读

关于反爬虫,看这一篇就够了
最好的语言PHP + 最好的前端测试框架Selenium = 最好的爬虫(上)
QueryList 简单、 灵活、强大的PHP采集工具,让采集更简单一点。