php爬虫

php可以通过curl扩展抓取页面数据,然后用preg_match/preg_match_all进行模式匹配或者使用DOM获取想要的数据。相关组件如下:
guzzle: 一个PHP的HTTP客户端,用来轻而易举地发送请求,并集成到我们的WEB服务上。可使用该组件发送HTTP请求。
DomCrawler:该组件简化了DOM操作,利用DOM提取需要获取的数据。

CURL

单线程:

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
// 创建一个新cURL资源
$ch = curl_init();

// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_URL, "http://www.example.com/");
// 启用时不将头文件的信息作为数据流输出。
curl_setopt($ch, CURLOPT_HEADER, 0);
// TRUE 将curl_exec()获取的信息以字符串返回,而不是直接输出。
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// 抓取URL并把它传递给浏览器
// 成功时返回 TRUE, 或者在失败时返回 FALSE。 然而,如果 设置了 CURLOPT_RETURNTRANSFER 选项,函数执行成功时会返回执行的结果,失败时返回 FALSE 。
curl_exec($ch);

// 关闭cURL资源,并且释放系统资源
curl_close($ch);
?>

CURLOPT_XXX请参考:php curl_setopt

curl抓取页面时,建议设置CURLOPT_TIMEOUT,避免因网络问题造成请求时间过长。

常用的CURLOPT_XXX:

  • CURLOPT_URL: 需要获取的 URL 地址,也可以在curl_init() 初始化会话的时候。

  • CURLOPT_HEADER: 启用时会将头文件的信息作为数据流输出。(true/false)

  • CURLOPT_RETURNTRANSFER: TRUE 将curl_exec()获取的信息以字符串返回,而不是直接输出。(true/false)

  • CURLOPT_PROXY: HTTP 代理通道。(http(s)://ip:port)

  • CURLOPT_USERAGENT: 在HTTP请求中包含一个”User-Agent:”头的字符串。

  • CURLOPT_TIMEOUT: 允许 cURL 函数执行的最长秒数。

  • CURLOPT_COOKIE: 设定 HTTP 请求中”Cookie: “部分的内容。多个 cookie 用分号分隔,分号后带一个空格(例如, “fruit=apple; colour=red”)。

  • CURLOPT_COOKIEFILE: 包含 cookie 数据的文件名,cookie 文件的格式可以是 Netscape 格式,或者只是纯 HTTP 头部风格,存入文件。如果文件名是空的,不会加载 cookie,但 cookie 的处理仍旧启用。(“/path/to/cookie_file”)

  • CURLOPT_COOKIEJAR: 连接结束后,比如,调用 curl_close 后,保存 cookie 信息的文件。(“/path/to/save_cookie_file_path”)

  • CURLOPT_POST: TRUE 时会发送 POST 请求,类型为:application/x-www-form-urlencoded,是 HTML 表单提交时最常见的一种。

  • CURLOPT_POSTFIELDS: 全部数据使用HTTP协议中的 “POST” 操作来发送。 要发送文件,在文件名前面加上@前缀并使用完整路径。 文件类型可在文件名后以 ‘;type=mimetype’ 的格式指定。 这个参数可以是 urlencoded 后的字符串,类似’para1=val1&para2=val2&…’,也可以使用一个以字段名为键值,字段数据为值的数组。 如果value是一个数组,Content-Type头将会被设置成multipart/form-data。 从 PHP 5.2.0 开始,使用 @ 前缀传递文件时,value 必须是个数组。 从 PHP 5.5.0 开始, @ 前缀已被废弃,文件可通过 CURLFile 发送。 设置 CURLOPT_SAFE_UPLOAD 为 TRUE 可禁用 @ 前缀发送文件,以增加安全性。

多线程:

示例代码:

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
<?php
// 创建一对cURL资源
$ch1 = curl_init();
$ch2 = curl_init();

// 设置URL和相应的选项
curl_setopt($ch1, CURLOPT_URL, "http://lxr.php.net/");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://www.php.net/");
curl_setopt($ch2, CURLOPT_HEADER, 0);

// 创建批处理cURL句柄
$mh = curl_multi_init();

// 增加2个句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);

$active = null;
// 执行批处理句柄
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);

while ($active && $mrc == CURLM_OK) {
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
if ($mrc > 0) {
// 错误处理
// curl_multi_strerror($mrc),返回可用错误代码所对应的错误信息,否则返回 NULL。
}
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}

// 关闭全部句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);

// 获取返回信息
$res1 = curl_multi_getcontent($ch1);
$res2 = curl_multi_getcontent($ch2);

curl_multi_close($mh);

curl_multi必须等待一次并发中所有请求都响应或请求超时后,才会执行下一次并发。

多线程能不能提高性能?

多线程在CPU密集型的作业下不会提高性能甚至更浪费时间,但在IO密集型作业下则可以提升性能(平均响应时间)。

工作线程数是不是越多越好?

  • 一个CPU核数有限,同时并发的线程数也有限,单核CPU设置1000个工作线程没有意义。
  • 线程切换需要开销,频繁地切换线程,反而会降低性能。

如果CPU是单核,多线程有意义吗,能提高并发性能吗?

即使是单核CPU,多线程也是有意义的。

  • 多线程可以让我们的代码/服务更加清晰,有些IO线程收发包,有些workder线程进行任务处理,有些timeout线程进行超时检测。
  • 如果有任务一只占用着CPU资源进行计算,那么增加线程并不增加并发。
  • 通常情况下,workder线程不会一直占用CPU进行计算,此时即使CPU是单核的,增加workder线程也可以提高并发,因为这个线程在休息的时候,其他线程可以继续工作。

Guzzle + DomCrawler

Guzzle安装

1
2
# Install Composer
curl -sS https://getcomposer.org/installer | php
1
$ composer require guzzlehttp/guzzle:~6.0

或者
在composer.json中添加依赖:

1
2
3
4
5
{
"require": {
"guzzlehttp/guzzle": "~6.0"
}
}

Guzzle官方文档

DomCrawler安装

1
$ composer require symfony/dom-crawler

DomCrawler官方文档

Guzzle + DomCrawler单线程抓取数据:

示例代码如下:

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
<?php

use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\DomCrawler\Crawler;

$requestOptions = [
'headers' => [
'User-Agent' => 'Mozilla/5.0 (X11...',
],
'cookies' => new CookieJar(),
'connect_timeout' => 5,
'timeout' => 5,
];

$res = '';
//微博用户tid
$tid = 'tid';
while (1) {
// 使用高匿名代理ip库
// 避免单ip被封禁
/* $httpProxy = 'tcp://ip:port'
if ($httpProxy) {
$requestOptions['proxy'] = [
'http' => $httpProxy,
];
} */

$client = new Client();
$uri = 'http://tw.weibo.com/' . $tid . '/follow';

// guzzle单线程抓取数据
try {
$res = $client->request(
'GET',
$uri,
$requestOptions
)->getBody()->getContents();
} catch (\Exception $e) {
continue;
}
break;
}

// DomCrawler提取所需数据
try {
$crawler = new Crawler($res);
$pgTxt = $crawler->filter('.pgTxt')->text();
preg_match('/共(.*?)頁/', $pgTxt, $page);

if (empty($page)) {
$page = 0;
} else {
$page = $page[1];
}
} catch (\Exception $e) {
$page = 0;
}

Guzzle + DomCrawler多线程抓取数据:

示例代码如下:

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
<?php

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

// 微博用户tid
$tid = 'tid';
$pageSize = 32;
// 微博关注列表
$uri = 'http://tw.weibo.com/api/user/follow';
$client = new Client();

// 异步请求
$requests = function ($pageCount) use ($pageSize, $uri, $tid, $client) {
for ($page = 1; $page <= $pageCount; $page++) {
$referer = 'http://tw.weibo.com/' . $tid . '/follow/p/' . $page;

yield function () use ($client, $pageSize, $uri, $tid, $referer, $page) {
return $client->requestAsync(
'POST',
$uri,
$this->getRequestOptions($referer, $page, $pageSize, $tid)
);
};
}
};

// 并发请求
$pageCount = 10;
$pool = new Pool($client, $requests($pageCount), [
// 并发数
'concurrency' => 10,
// 请求成功回应
'fulfilled' => function (Response $response, $index) use ($uid, $tid, $offset) {
echo $index . PHP_EOL;
$body = $response->getBody()->getContents();
// DomCrawler
$crawler = new Crawler($body);
try {
$tids = $crawler->filter('.addFollow')->each(function (Crawler $item) {
return $item->attr('uid');
});
} catch (\Exception $e) {
return;
}

if (empty($tids)) {
return;
}

// IO存储任务放入队列执行
// $job = (new SaveTids($uid, $tid, $tids, $index, $offset))
// ->onQueue('curl');
// dispatch($job);
},
// 请求失败回应
'rejected' => function ($reason, $index) use ($tid) {
// 请求失败后放入队列继续抓取
// $job = (new SaveUndealtTids($tid, $index))->onQueue('curl');
// dispatch($job);
return;
},
]);

// Initiate the transfers and create a promise
$promise = $pool->promise();
// Force the pool of requests to complete.
$promise->wait();

Guzzle异步请求文档
Guzzle并发请求文档

User-Agent列表

部分User-Agent列表如下:

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
"Mozilla/5.0 (X11; CrOS x86_64 5841.83.0) AppleWebKit/537.36 (KHTML like Gecko) Chrome/36.0.1985.138 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.132 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.18 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.18",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.4 Safari/534.30",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.15 Safari/534.13",
"Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.16 (KHTML, like Gecko) Chrome/10.0.648.205 Safari/534.16",
"Mozilla/5.0 (Linux; U; Linux Ventana; en-us; Transformer TF101G Build/HTJ85B) AppleWebKit/534.13 (KHTML, like Gecko) Chrome/8.0 Safari/534.13",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.59 Safari/537.36",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.34 Safari/534.24 XiaoMi/MiuiBrowser/1.0",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0 Safari/537.2",
"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/531.0 (KHTML, like Gecko) Chrome/3.0.195.0 Safari/531.0 SE 2.X",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0 Safari/532.9",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.8 (KHTML, like Gecko) Chrome/6.0.397.0 Safari/533.8",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/537.36 (KHTML, like Gecko, Google-Publisher-Plugin) Chrome/27.0.1453 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.2; en-US) AppleWebKit/534.7 (KHTML, like Gecko) Chrome/7.0.517.41 Safari/534.7",
"Mozilla/5.0 (X11; FreeBSD; U; Viera; cs-CZ) AppleWebKit/537.11 (KHTML, like Gecko) Viera/3.3.3 Chrome/23.0.1271.97 Safari/537.11",
"Mozilla/5.0 (X11; U; Unix; en-US) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15 Surf/0.6",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.0 AOL/9.7 AOLBuild/4343.3029.gb Safari/537.1",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.94 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 6.1;de) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/1.0.154.43 Safari/525.19",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.1.249.1025 Safari/532.5",
"Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Chrome/4.0.222.12 Safari/532.2",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.8 (KHTML, like Gecko) Chrome/20.0.1105.2 Safari/536.8",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.2 Safari/537.22",
"Mozilla/5.0 (Windows NT 5.0) AppleWebKit/5351 (KHTML, like Gecko) Chrome/15.0.849.0 Safari/5351",
"Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36 davecampbell",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.77 Safari/535.7",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/5321 (KHTML, like Gecko) Chrome/14.0.867.0 Safari/5321",
"Mozilla/5.0 (Windows; U; Windows NT 5.2; en-US) AppleWebKit/534.31 (KHTML, like Gecko) Chrome/17.0.558.0 Safari/534.31 UCBrowser/57B75BEEF",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.118 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.94 AOL/9.7 AOLBuild/4343.4025.de Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/530.9 (KHTML, like Gecko) Chrome/2.0.180.0 Safari/530.9",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; de) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.2.149.27 Safari/525.13",
"Mozilla/5.0 (X11; FreeBSD amd64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Dragon/46.9.15.424 Chrome/46.0.2490.86 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.0.10802 Safari/537.36",
"Mozilla/5.0 (X11; FreeBSD i386) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.112 Safari/535.1",
"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.3.154.9 Safari/525.19",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.107 Safari/537.36",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.4.154.33 Safari/525.19",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.1.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.2.0.0 Safari/537.17",
"Mozilla/5.0 (Windows NT 6.0; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.2.0.0 Safari/537.11",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.1.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.47 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.4.0.0 Safari/537.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.1.0.0 Safari/537.22",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.2.2.0 Safari/537.31",
"Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.2.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.1.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.1.0.0 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.4.9999.1836 Safari/537.31 BDSpark/26.4",
"Mozilla/5.0 (X11; U; Linux x86_64; en-US) AppleWebKit/540.0 (KHTML,like Gecko) Chrome/9.1.0.0 Safari/540.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/52.2.90 Chrome/46.2.2490.90 Safari/537.36",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.2357.124 Safari/537.36",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.4.2526.80 Safari/537.36",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2655.0 Safari/537.36",

更多User-Agent列表可见:User-Agent列表

高匿名代理IP库

为防止单ip爬虫遭遇封禁,可选择高匿名代理ip库,若爬虫目标服务厂商存在一些高匿名代理ip黑名单,即使使用高匿名代理ip也会遭遇封禁。可用代理IP库如下:

免费代理池(IPProxyPool):qiyeboy/IPProxyPool

如何抓取动态网页数据?

动态网页由js脚本动态生成网页,仅使用curl无法获取完整页面源码,可使用phantomjs或者selenium模拟浏览器操作。详情请见:php抓取动态页面数据