PHP curl实现多进程并发高效率采集爬虫
演示代码
运行效果(图1)
运行效果(图2)
主要封装函数
multi_process();
根据参数,创建指点数目的子进程。
亮点功能1:子进程各种异常退出,比如segment fault, Allowed memory size exhausted等,中断一个子进程后,父进程会重新fork一个新进程顶上去,保持子进程数量。如果子进程里完成任务(比如判断tid达到10000),可以在子进程里exit(9),父进程会收到这个退出状态(9),然后等待所有子进程退出,最后退出自身进程。
亮点功能2:与curl封装函数一起实现了一统计功能,在程序关闭后会显示出一些主要的统计信息(图2的底部)。
mp_counter();
在父进程以及所有子进程之间通信,负责协调分配各子进程的任务,使用了锁机制。可以设置’init’参数重置计数,可以设置每次更新计数的值。
curl_get();
对curl相关函数的封装,加入了大量的错误机制,支持POST,GET,Cookie,Proxy,下载。
mp_msg();
实现规范之一就是,每条任务处理完,只输出一行信息。
亮点功能:这个函数会判断终端的高度和宽度,实现每一屏内容会显示一条统计信息(图1的紫色行),便于观察程序的执行情况,控制每一行输出的长度,保持一条信息不会超过一行。
rand_exit();
众所周知,PHP存在内在泄露的问题,所以每一个子进程里执行一定次数的任务后就退出,由multi_process()负责自动建立新的子进程(如图1中的绿色行)。
程序效率
本次测试使用的是Vultr的最低配置机器,1 CPU(3.6GHz),768MB RAM,美国LA机房(一定程度上影响了抓取速度)。
执行了十多分钟后,统计信息如下:
运行期间内存占用统计(while true; do psmem | grep php;sleep 10; done)如下:
vmstat 1命令结果如下
iftop带宽监控如下:
50个子进程,执行11分55秒,抓取50951次,按这个速度计算,一天可以抓取615万次。
所有进程(1父进程+50子进程)共占用内存约60MB,占用CPU约20%(1核心),带宽占用约7-8Mbps。
不同进程数量的抓取速度对比:
1个进程
100个进程
多进程的封装几乎完美,但curl由于它的功能太过于丰富和强大,可能永远也无法达到完美
代码如下
curl.lib.php
代码如下 | 复制代码 |
<?php // 命令行颜色输出 /* /* /* /* // cookie和临时文件目录 // 清除过期的cookie文件和下载临时文件 /** /** /** if(!$header_text){ // 处理状态码 /** return curl_func($url, $method, $data, $path, $proxy); /**
/** function img_down($url, $path_pre){ function get_img_ext($path){ /** } /** /** /** /** // 控制台输出颜色 // 去除URL中的/../ // 去除实体转码 // 统计数据 for($i = 0; $i < curl_config_get('retry'); $i++){ // 初始化 // 设置超时 // 接收网页内容到变量 // 忽略SSL验证 // 设置referer, 在文件里配置的优先级最高 // 设置HTTP请求标识,在文件里配置的优先级最高 curl_setopt($ch, CURLOPT_USERAGENT, $useragent); // 出口IP // 设置代理 // 设置允许接收gzip压缩数据,以及解压,抓取HEADER时不使用(获取不到正确的文件大小,影响判断下载成功) // 遇到301和302转向自动跳转继续抓取,如果用于WEB程序并且设置了open_basedir,这个选项无效 // 启用cookie // 设置post参数内容 // 设置用于下载的参数 // 仅获取header // 抓取结果 // 调试curl时间,记录连接时间,等待时间,传输时间,总时间。 // 关闭CURL句柄 // 如果CURL有错误信息则判断为抓取失败,重试 // 统计流量 // 对结果进行处理 if(in_array($status_code, array_merge(range(400, 417), array(500, 444)))){ if(empty($savepath)){ // 分析页面编码 // 转码条件:1)匹配到编码, 2)返回编码不为空, 3)匹配到的编码和返回编码不相同 // iconv如果失败则返回空白页 // 统计下载文件量 return TRUE; // 如果是下载或者抓取header,并且错误代码为6(域名无法解析),则不输出错误。失效的图片引用太多了。 // 统计数据 return FALSE; /** // 多并发下建议关闭黄色错误输出 if(php_sapi_name() != 'cli'){ if(!in_array($color, $available_msg)){ echo "{$reverse}".$colors[$color]."({$method})[cURL ERROR: {$msg}] {$url}{$end}\n"; /** /** function relative_to_absolute($content, $url) { preg_match("/(http|https|ftp):\/\/[^\/]*/", $url, $preg_base); preg_match("/(http|https|ftp):\/\/.*\//", $url, $preg_full); return $content; /** $cookie_files = glob("{$cookie_dir}curl_*_pid_*"); foreach($files as $file){
}
function curl_rand_ua_pc(){ function curl_rand_ua_mobile(){ function curl_config_get($key){ if(!empty($curl_config[getmypid()][$key])){ function curl_config_set($key, $val){ function curl_set_ua($ua){ function curl_set_referer($referer){ function curl_set_retry($retry){ function curl_set_conntimeout($conntimeout){ function curl_set_fetchtimeout($fetchtimeout){ function curl_set_downtimeout($downtimeout){ |
process.lib.php
代码如下 | 复制代码 |
<?php declare(ticks = 1); // 中断信号 // 命令行颜色输出 // 程序开始运行时间 // 父进程PID // 文件保存目录,/dev/shm/是内存空间映射到硬盘上,IO速度快。 // 清理过期资源(文件和SEM信号锁),每次程序执行都需要调用,清除掉之前执行时的残留文件。 // 判断是否在子进程中 /** // 父进程PID或者自身PID // 系统启动时间 // 进程启动时间 // 由父进程ID确定变量文件路径前缀 // 由于系统启动时间和当前父进程启动时间(jiffies格式)确定计数使用的文件 // 更新计数,先锁定 if(!file_exists($cur_path)){ $counter = 0; // 更新记数, 继续研究下判断init不能用== // 写入计数,解锁 return $new_counter; /** if(empty($num)){ // 记录进程数量,统计用 // 子进程数量 // 任务完成标识 while(TRUE) { // 清空子进程退出状态 // 如果任务未完成,并且子进程数量没有达到最高,则创建 // 注册父进程的信号处理函数 //$stat && pcntl_signal(SIGINT, "signal_handler"); echo "{$reverse}{$green}[+]New Process Forked: {$pid}{$end}\n"; // 1,注册一个信号,处理函数直接exit(),目的是让子进程不进行任何处理,仅由主进程处理这个信号 // 注册信号后直接返回,继续处理主程序的后续部分。 // 子进程管理部分 // 统计信息 // 子进程退出状态码为9时,则判断为所有任务完成,然后等待所有子进程退出
if(strpos($script, '/') === FALSE || strpos($script, '..') !== FALSE){ return $path; function final_stat(){ // 时间统计 // curl抓取统计 $down_total = mp_counter('down_total', 0); $header_total = mp_counter('header_total', 0); $download_size = hs(mp_counter('download_size', 0)); echo " Request Stat: Fetch({$fetch_success}/{$fetch_total}), Header({$header_success}/{$header_total}), "; // curl流量统计 // 效率统计 echo "========================================================================{$end}\n"; /** function sub_process_exit(){ function hnum($num){ /**
/** if(!function_exists('get_ppid')){ // 以进程(多进程运行时,使用父进程)为单位,每个进程使用一个锁。 // 解除锁 // 清理资源(文件和SEM信号锁) // 清除sem的文件和信号量 // 清除mp_counter的文件(仅此类型文件不可重用,所以严格处理,匹配系统启动时间和进程启动时间) // 清除文件 // 系统启动时间 // 如果是在子进程中调用,则取父进程的启动时间。如果不是在子进程中调用,则取自身启动时间。时间都是jiffies格式。 // 防止PHP进程内存泄露,每个子进程执行完一定数量的任务就退出。 // 单次的任务结果输出函数 // 整理统计信息 // cron方式运行 $lock = sem_lock('mp_msg'); $t_cols = mp_counter('t_cols', 0); if($t_lines <= 1){ $process_num = mp_counter('process_num', 0); $fetch_total = mp_counter('fetch_total', 0); echo "{$reverse}{$purple}"; } |
我习惯设置的日志路径是这样
/home/www/logs/域名.log
比如
/home/www/logs/www.yundaiwei.com.log
为了方便管理,日志需要按天保存在一个文件中,并且保留指定天数的日志,超过时间的就删除。
分享一下脚本
#!/usr/bin/php
<?php
$logdir = '/home/www/logs/';
// 保留天数含当天
$log_save_day = 7;
$files = glob("{$logdir}/*");
foreach($files as $path){
$filename = basename($path);
preg_match("/(\d{8})\.log/", $filename, $preg);
$date = @$preg[1];
if(empty($date)){
// 当天日志,更改文件名
$newpath = $logdir . '/' . str_replace('log', date('Ymd',strtotime("-1 day")).'.log', $filename);
rename($path, $newpath);
echo "$path >>> $newpath\n";
}else{
// 超过保留天数,删除
if(time()+10 - strtotime($date) > 3600*24*$log_save_day){
unlink($path);
echo "$path delete!\n";
}
}
}
shell_exec('/etc/init.d/nginx reload &> /dev/null');
下面我们来看一篇关于 提交PHP组件到Packagist 发布自己的Composer包的教程,希望这篇文章能够帮助到各位朋友哦。Composer是PHP的一个依赖管理工具,它使得PHP焕发新的生机,有了现代化的WEB开发规范,Packagist是PHP组件的库,也有其他的镜像。
在Packagist上提交了一个自己开发的PHP组件,这样其他开发者就可以使用Composer使用这个包了。这个组件并没什么功能,主要是看看提交PHP组件的流程,并记录了过程中遇到的问题及解决方法,以供参考。
提交PHP组件步骤:
1.新建一个项目目录,创建一个composer.json文件,格式如下:
PHP
{
"name": "your-vendor-name/package-name",
"description": "A short description of what your package does",
"require": {
"php": "^5.3.3 || ^7.0",
"another-vendor/package": "1.*"
}
}
这个json格式的文件中包含组件的基本信息,这里还差自动加载的方式,要根据具体开发模式指定自动加载方式,这里require可以指定这个组件依赖的其他组件,composer都会自动解决依赖。
附上完整的composer.json内容作为示例:
JavaScript
{
"name": "tanteng/laravel-page-loaded-time",
"description": "Display page loaded time",
"keywords": ["laravel","performance"],
"homepage": "https://github.com/tanteng/laravel-page-loaded-time",
"license": "MIT",
"authors": [
{
"name": "tanteng",
"email": "tanteng@qq.com",
"homepage": "http://www.tanteng.me",
"role": "Developer"
}
],
"require": {
"php": "^5.3.3 || ^7.0",
"laravel/framework": "~4.0"
},
"autoload": {
"psr-4": { "Loading\\": "" }
}
}
2.开发组件功能
要注意遵循psr规范,使用命名空间。
3.把组件提交到Github上
提交组件到Packagist之前需要先把代码提交到github上,在Packagist只需填写组件的github地址。
4.提交组件地址到Packagist
这样就完成的PHP组件提交到Packagist的过程,具体请参见Packagist官网。
问题:使用composer require找不到组件
组件提交到Packagist上,提示发布成功了,但是使用composer命令却找不到组件:
PHP
composer require tanteng/laravel-page-loaded-time
如图:
由于我的composer使用的国内镜像,猜测可能是没有同步的原因,使用这个命令把“源”改回去还是不行。
PHP
composer config -g repo.packagist composer https://packagist.org
原来我的组件还没有在github上发布正式,这个时候还是开发版本dev-master.应该加上dev-master版本。
PHP
composer require tanteng/laravel-page-loaded-time:dev-master
果然指定了dev-master版本可以成功更新组件,但是这样不行,我们要有一个正式版本。
github发布版本
进入组件的github主页,找到导航上“releases”,点击进去如图页面,就可以创建一个版本,填写好信息之后即可发布版本。
按照页面上的提示填写内容,完成后发布。发布版本后,通过composer require发现还是找不到包。
设置自动更新版本
原来还要在Github上配置一下自动更新。具体步骤参考:https://packagist.org/about#how-to-update-packages
我直接通过手动的方式发送curl请求来设置,这样还简单一点,不过这样每次发新的版本都需要这样请求一下:
PHP
curl -XPOST -H'content-type:application/json' 'https://packagist.org/api/update-package?username=tanteng&apiToken=secret' -d'{"repository":{"url":"https://github.com/tanteng/laravel-page-loaded-time"}}'
返回{“status”:”success”}表示成功。
再打开https://packagist.org/packages/tanteng/laravel-page-loaded-time,发现已经是正式版本了。
我用的是composer国内镜像,因为众所周知的原因,连代码仓库也要被墙,我服!等了几个小时再试,这个时候镜像同步更新了,再次输入:composer require tanteng/laravel-page-loaded-time,这个时候可以成功更新了。如图,vendor文件夹下也自动装载了依赖的其他组件
命名规则其实还是有一点的要求会比较好,否则不统计也难看了,下面整理了一篇关于PHP变量命名规则详解,希望下面的文章对各位有用。PHP变量命名规则
1、变量以美元符号$开头。如$name,$age。
2、美元符号$后面的第一个字符不可以是数字,只能是下划线_或者字母。如$1_1这样的变量是错误的。
3、除了下划线_外,变量不允许出现任何空格或标点符号。也就是说变量名只能包含:a-z、A-Z、0-9 以及下划线_。
4、PHP变量名是区分大小写的。如$name与$Name是两个不同的变量。
PHP变量命名规则其余注意事项
1、当用两个或两个以上的单词命名变量时,可以将除第一个单词以外的所有单词的首字母大写。如$myName、$yourFamilyName。
2、以下划线_开始命名的变量通常代表特殊的变量。如在类中创建受保护的属性、PHP预定义变量($_GET)、全局数组等。
3、定义变量的时候,不要贪图简短,而应该使用具有描述性的名称定义变量。
学而不思则罔,思而不学则殆。希望您在学习PHP的道路上一帆风顺,并成为PHP编程艺术大师。
以上便是PHP变量命名规则的全部内容。以下是废话,如果您有时间,不妨粗略阅读,也许您能产生共鸣。
PHP变量命名规则
从大学开始到现在,陆陆续续接触过各种编程语言。如客户端语言Javascript,Actionscript;服务器端语言C++,Java,ASP.net,PHP等等,不一而足。在所有这些语言中,我最想学的是PHP。不为别的,就因为PHP的读法:屁H屁,说起来琅琅上口,韵味十足。今天看到PHP变量命名规则这一节,索性就随便写一点东西,聊以打发无聊的光阴。
对PHP的学习,一直时断时续。年轻时候要学好PHP的雄心壮志如今差不多荡然无存了。
有人说,活在这样一个社会里,没有人能一尘不染地活着。这是个怎样的社会?我不太清楚。不过我听人说,这是个聋子听见哑巴说瞎子看见了爱情的扯淡时代。不完全信,但也无法完全不信。
不管怎样,既然活着是不容易的,那么就且活且珍惜吧。
PHP是我想珍惜的。
于是,今天翻了一翻从图书馆借来的新书——PHP动态网页设计(第二版,作者David Powers)。
看到PHP变量命名规则一节。这些非常基础的内容,在时隔数年之后重新温习,倒是真有温故而知新的感觉。
为了使印象更为深刻,也为了实践,好记性不如烂笔头的真理,在这不太安分的夜里,我决定写这篇文章,献给曾经想成为PHP高手的自己。
1、变量以美元符号$开头。如$name,$age;
2、美元符号$后面的第一个字符不可以是数字;
3、除了下划线_外,变量不允许出现任何空格或者标点符号;
4、PHP变量名是区分大小写的。如$name与$Name是两个不同的变量。
其余注意事项
1、当用两个或两个以上的单词命名变量时,可以将除第一个单词以外的所有单词的首字母大写。如$myName;$yourFamilyName;
2、以下划线_开始命名的变量通常留给特殊的情况使用。如在类中创建受保护的属性;PHP预定义变量:全局数组等;
3、定义变量的时候,不要贪图简短,而应该使用具有描述性的名称定义变量。
例子
面是分别用骆驼式命名法和下划线法命名的同一个函数:
printEmployeePaychecks();
print_employee_paychecks();
第一个函数名使用了骆驼式命名法——函数名中的每一个逻辑断点都有一个大写字母来标记;第二个函数名使用了下划线法----函数名中的每一个逻辑断点都有一个下划线来标记。
骆驼式命名法近年来越来越流行了,在许多新的函数库和Microsoft Windows这样的环境中,它使用得相当多。另一方面,下划线法是c出现后开始流行起来的,在许多旧的程序和UNIX这样的环境中,它的使用非常普遍。
编辑本段应用概述
骆驼式命名法(Camel-Case)是电脑程式编写时的一套命名规则(惯例)。
骆驼式命名法就是当变量名或函式名是由一个或多个单字连结在一起,而构成的唯一识别字时,第一个单词以小写字母开始;第二个单词的首字母大写或每一个单词的首字母都采用大写字母,例如:myFirstName、myLastName,这样的变量名看上去就像骆驼峰一样此起彼伏,故得名。
骆驼式命名法(Camel-Case)一词来自 Perl 语言中普遍使用的大小写混合格式,而 Larry Wall 等人所著的畅销书《Programming Perl》(O'Reilly 出版)的封面图片正是一匹骆驼。
骆驼式命名法的命名规则可视为一种惯例,并无绝对与强制,为的是增加识别和可读性。
驼峰法(小驼峰法)
变量一般用小驼峰法标识。驼峰法的意思是:除第一个单词之外,其他单词首字母大写。譬如
int myStudentCount;
变量myStudentCount第一个单词是全部小写,后面的单词首字母大写。
Pascal法(大驼峰法)
相比小驼峰法,大驼峰法把第一个单词的首字母也大写了。常用于类名,函数名,属性,命名空间。譬如
publicclass DataBaseUser;
相关文章
- 关于mysql效率优化一般通过以下两种方式定位执行效率较低的sql语句。通过慢查询日志定位那些执行效率较低的 SQL 语句,用 --log-slow-queries[=file_name] 选项启动时, mysqld 会 写一个包含所有执行时间超过 long_quer...2015-11-08
- 这篇文章主要介绍了如何用Node.js编写内存效率高的应用程序,对Node.js感兴趣的同学,可以参考下...2021-05-01
- 这篇文章主要介绍了python基于opencv检测程序运行效率,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下...2020-05-09
- 在本篇文章里小编给大家分享的是一篇关于mysql事务对效率的影响分析总结内容,有需要的朋友们可以跟着学习下。...2021-10-24
- JavaScript数组去重是前端面试酷爱的问题,问题简单而又能看出程序员对计算机程序执行过程的理解如何。数组去重的方法有很多,到底哪种是最理想的我不清楚。于是我测试了下数组去重的效率。测试二十万个数据,随着数据越多效率很明显的就体验了出来。下面来一起看看吧。...2016-10-25
- pcntl中的php必须要安装pcntl才可以实现多线程了,在网上找到许多的关于pcntl安装教程,下面整理了一篇比较完整的关于php pcntl安装与使用方法。 pcntl中php实现多进...2016-11-25
- 这篇文章主要为大家详细的介绍了Redis高效的原因以及分析了Redis高效的数据结构,有需要的朋友可以借鉴参考下,希望能够有所帮助...2021-09-27
- CuPy是一个开源矩阵库,使用NVIDIA CUDA加速。CuPy使用Python提供GPU加速计算。CUPY使用CUDA相关库,包括 CuBLAS、CUDNN、Curand、CuoSver、CuPaSeSE、Cufft和NCCL,以充分利用GPU架构...2021-11-10
- 在遇到数据之间的联系很复杂,建表就很纠结,到底该怎么去处理这些复杂的数据呢,是单表查询,然后在业务层去处理数据间的关系,还是直接通过多表连接查询来处理数据关系呢...2021-09-27
使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的方法
这篇文章主要给大家介绍了关于如何使用C# CefSharp Python采集某网站简历并且自动发送邀请短信的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面来一起看看吧...2020-06-25php中 strtr 和 str_replace 的效率问题
下面小编就为大家带来一篇关于php中 strtr 和 str_replace 的效率问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 在网上看了...2017-07-06- <?php header("Content-Type: text/html; charset=gb2312"); session_start(); set_time_limit(0); $str = file_get_contents('http://music.soso.com/'); pr...2016-11-25
如何采集搜狗微信搜索的内容(SogouEncrypt版) -- hzw
在搜狗微信搜索中,之前微信的内容是用 http://weixin.sogou.com/gzhjs?cb=sogou.weixin.gzhcb&openid=oIWsFt1OaL2XHiDx6809O8q2KZ5A&page=1 这类格式来调用数据的,openid...2016-05-19- 我们一起来看一个PHP CLI模式下PCNTL扩展实现多进程服务的例子,有面要了解这个问题的朋友可以和小编一起来看看。 PHP可通过PCNTL扩展实现进程控制,如进程创建,信...2016-11-25
- 1、用i =1代替i=i 1。符合c/c 的习惯,效率还高。 2、尽可能的使用PHP内部函数。自己编写函数之前要具体查阅手册,看有没有相关的函数,否则费力不讨好。 3、能使用单引号...2016-11-25
- 采集网页上图片的主要关键是在怎么解析出页面代码里那些img标签的src属性...2021-09-22
- <? / / PHP的新闻抓取由Neil Moomey,。 / /你可以自由的使用此代码作为您的愿望。 / /请确保您可以从任何网站,你抓从标题许可。 / /你可能需要写上您的服务器上...2016-11-25
- 这篇文章主要介绍了pytorch 限制GPU使用效率详解(计算效率),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2020-06-28
- 这篇文章主要介绍了多线程如何解决for循环效率的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-17
- 这篇文章主要介绍了IDEA提高开发效率的7个插件(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-07-10