一次简单的php爬取图片

在知乎上看到一篇写关于python爬取知乎回答下面的图片的文件,看着感觉蛮简单的,就想试着写一下,但是自己没有学python,并不会…所以想着用php来实现一下。
首先分析下过程吧,对了,先说说下目标问题是这个https://www.zhihu.com/question/58498720,只是这个问题的图片比较多,嗯,就是这样。

案例一

过程分析

因为知乎是 ajax 异步加载,所以我们要先找到加载的请求链接

  1. 打开浏览器的 network,分析请求链接,得到的是一个很长的请求,分析后去掉请求中一些无用的参数,最后得到的是https://www.zhihu.com/api/v4/questions/58498720/answers?include=content&limit=1&offset=0,其中limit是一次显示几条数据,offset是从第几条回答开始,返回的 json 格式的数据

  2. 分析返回的内容,得到图片在 data-original 这个参数后面,但是这个参数后面的图片链接是重复了一次的,就是说一张图片有两次这个链接,所以我们要做的就是把 data-original 后面的图片链接用正则取到后去除重复的,存入到一个数组中,得到一个图片链接数组

  3. 接下来要做的就是遍历图片数组中的图片链接,然后依次下载到本地即可。

大概的流程就是这样,还是很简单的

示例代码

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
<?php
ini_set('max_execution_time', '0');//将PHP的最大执行时间不做限制
for($i=0;$i<=7;$i++) { //这个循环控制下载哪一个问题的图片
$url = "https://www.zhihu.com/api/v4/questions/58498720/answers?include=content&limit=1&offset={$i}&sort_by=default";
echo $url."<br>";
$ch = curl_init(); //curl初始化
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //这个是重点,规避ssl的证书检查。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 跳过host验证
$json = curl_exec($ch); //获取返回的json数据
curl_close($ch);
if(!strpos($json,"data-original=")){ //这个if判读这个回答是否有图,如果无图的话就可以直接跳过这个回答
echo "退出第{$i}次无图循环";
continue;
}
$p = "/data-original=.*?[\s]/";
preg_match_all($p, $json, $arrs); //通过正则取得包含图片链接的所有参数
$n = 1; //这个变量是用来去除一半的重复
foreach ($arrs as $arr) { //通过正则匹配到的url是重复了一次的 所以去掉重复的一次
foreach ($arr as $num) {
if (($n % 2) == 0) {
$num = str_replace("data-original=", "", $num); //去掉其中多余的一些字符
$num = str_replace('\\', "", $num);
$num = str_replace('"', "", $num);
$array[] = $num;
}
$n++;
}
}
$n = 1; //这个变量控制下载过来的图片名字,避免执行时间太快,出现重名,导致部分图片被覆盖,图片用当前时间+$n命名
foreach ($array as $one) { //遍历图片链接数组,依次下载
$ch = curl_init(); //curl初始化
curl_setopt($ch, CURLOPT_URL, $one);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //这个是重点,规避ssl的证书检查。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 跳过host验证
$img = curl_exec($ch);
curl_close($ch);
$time = date("YmdHis", time());
$file = fopen("./download/{$time}-{$n}.jpg", "w");
fwrite($file, $img);
fclose($file);
$n++;
}
}
echo "所有图片下载完成";

这个有一个问题就是知乎上爬取到的图片是重复的,不知道是知乎的反爬还是其他原因造成的。

案例二

因为前一个案例中,爬到图片重复,而且代码都是面向过程,没有封装成函数,有点混乱,所以又重新写了一个爬取另一个网站图片的例子
目标网站链接https://www.dbmeinv.com/index.htm

过程分析

这个整体思路和爬取知乎的思路大致一样,但因为这个不是 ajax 异步的,所以获取图片链接就是从 html 源码中获取。同样也是用正则取得img标签中的数据,然后取出多余字符。最后得到一个图片链接数组。

示例代码

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
<?php
ini_set('max_execution_time', '0');//将PHP的最大执行时间不做限制
class Iamge{
private $url; //存储最终去爬取的链接的url,因为这个网站下面还有不同分类,
private $getPage; //一次要爬取的页数
private $indexUrl= "https://www.dbmeinv.com/"; //网站首页地址
private $path; //图片存储路径

public function __construct($path,$getPage=1)
{
$this->path = $path;
$this->getPage = $getPage;
$this->download();
}

private function getHtml(){ //获取html
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //这个是重点,规避ssl的证书检查。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 跳过host验证
$html = curl_exec($ch);
curl_close($ch);
return $html;
}

private function getImgSrc(){ //获取图片链接
$html = $this->getHtml();
$p = "/src=.*?[\s]/";
preg_match_all($p, $html, $arrs);
unset($html);
foreach ($arrs as $arr){
foreach ($arr as $value){
if(strpos($value,".jpg")){
$imgArr[] = $value;
}
}
}
unset($arrs);
foreach ($imgArr as $arr){
$temporary = str_replace("src=\"","",$arr);
$temporary = str_replace("\"","",$temporary);
$imgArrTwo[] = str_replace("bmiddle","large",$temporary); //这里进行替换是因为如果不替换的话,直接爬取到的链接是缩略图
}
return $imgArrTwo;//返回图片链接数组
}

private function imgDownload(){ //遍历图片链接下载,依次下载
$n = 1;
foreach ($this->getImgSrc() as $src){
$ch = curl_init();
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //这个是重点,规避ssl的证书检查。
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); // 跳过host验证
curl_setopt($ch, CURLOPT_URL, $src);
$img = curl_exec($ch);
$time = date("YmdHis", time()); //设置图片下载时间
$file = fopen($this->path.'/'.$time.$n.".jpg", "w");
fwrite($file, $img);
$n++;
}
fclose($file);
}

private function download(){ //下载其他页的图片
for($i=1;$i<=$this->getPage;$i++){
$this->url = $this->indexUrl."?pager_offset={$i}";
echo "正在爬取第{$i}页\n";
$this->imgDownload();
}
}
}

fwrite(STDOUT,"请输入你要爬取的页数:"); //可从cmd运行php,从cmd获取用户的输入
$page = trim(fgets(STDIN));
fwrite(STDOUT,"请输入图片保存地址:"); //可从cmd运行php,从cmd获取用户的输入
$path = trim(fgets(STDIN));
$obj = new Iamge($path,$page);
echo "爬图完成!";

这个案例中将代码封装成了类,每一步对应一个方法,代码的逻辑性更清晰一点,同时也可从cmd直接运行脚本,然后输入爬取的页数来控制爬取的图片内容,但也还有一些可以改进的地方,比如从第几页爬到第几页,现在是只能每次从第一页开始爬。
还有爬不同分类下的内容,还只能通过修改源码完成,不能通过在 cmd 输入来区别。
但总的来说,一个简单的小爬虫小项目基本功能已经完成了。