php 获取字符串首歌,PHP爬虫 网易云音乐歌手和热门歌曲信息抓取

序章

PM最近问我要网易云的歌手的热门歌曲的信息,作为数据分析。说起网络爬虫我们都不陌生,我们分析网站的HTML的格式和URL的通用格式来写相应的算法。然后请求对应的URL来获取HTML字符串,因此总的来说,爬虫的本质就是请求和字符解析。

一.分析页面布局。

首先我们来分析网易云音乐的HTML构成(多图预警)。我们来看网易云的歌手的网页构成。

972054613890

图1

972054613890

图2

图1、图2中分别标注了三个地方,是我们分析一个网页的时候,需要注意的地方。

1.分析URL

2.分析菜单列表。

很容易的能够看到,网易云歌手的分布是按照【类别】来分的。我们在获取每个歌手的信息的时候,又id来表示的,其中图2中地址栏中的地址进行分析:http://music.163.com/discover/artist/cat?id=1001&inital=65

1,有前端知识的同学肯定知道,地址栏中的 “#”是前端的路由。所以我们再实际操作的时候,需要把#去掉。在地址中,http://music.163.com/discover/artist/cat?id=1001&inital=65,id=1001表示的是歌手的类别ID,inital=65表示的是歌手的按照字母排序的歌手名字类别ID。

详细来说

id=1001 表示的是 【华语男歌手】

inital=65 表示的是 【首字母为A的名字】

(读者可以访问http://music.163.com/discover/artist/cat?id=1001&inital=65查看一下网页的源码。)

我们可以通过获取分析HTML源码来获取页面上的所有的【类别ID】和【歌手名字类别ID】,进而获取所有的【歌手列表页面】

例如:

如下图

972054613890

图3

图中很容一分析的页面上的歌手类别ID,华语男歌手ID为 1001、华语女歌手ID为1002、华语组合/乐队ID为1003。

同理我们很容分析出来歌手的名字类别ID如图4

972054613890

图4

很容易得知:热门歌手的ID=-1、首字母为A的ID=65、首字母为B的ID=66、首字母为C的ID=67。

由上图的分析很容易知道,假如类别有N个,名字类别有M个。最后组合在一起的URL一共有N*M个。

整体思路我们分析完了。

接下来该分析源码,处理字符串来获取了我们想要的类别ID 和 名字类别ID。

/**

* 发送请求

* @param null $url 请求的URL

* @return bool|string 返回的信息

*/

function sendRequest($url=null){

$handle = null;

if (empty($url)){

$handle = fopen($this->url, "rb");

}else{

$handle = fopen($url, "rb");

}

$contents = stream_get_contents($handle);

fclose($handle);

return $contents;

}

/**

* 获取歌手歌曲主页的URL

* @param $html_str

* @return array

*/

function getSingerHomeUrl($html_str){

$typeMap = [];

$rltArr = [];

$urlRsltArr = [];

$dom = HtmlDomParser::str_get_html($html_str);

$elems = $dom->find('li');

foreach ($elems as $key => $value){

$aas = $value->find('a');

foreach ($aas as $k=>$v){

if (strpos(trim($v->href),'discover/artist/cat') !== false ){

if (strpos($v->href,'initial') !== false){

$rltArr[] = BASE_URL.$v->href;

}else{

if (is_numeric(trim($v->href,'/discover/artist/cat?id='))){

$typeMap[trim($v->href,'/discover/artist/cat?id=')] = $v->text();

}

}

}

}

}

foreach ($typeMap as $id=>$type){

foreach ($rltArr as $url){

$urlRsltArr[$type][] = str_replace('1001',$id,$url);

}

}

return $urlRsltArr;

}

其中我们用到了一个工具HtmlDomParser,这是一个专门用来解析HTML的SDK,我们可以在github上很容易搜到,在这里我用的是composer来管理这些第三方框架的。

composer require sunra/php-simple-html-dom-parser

既然说到HtmlDomParser,我就多说一句。

传进去html字符串,获取一个DOM对象

$dom = HtmlDomParser::str_get_html($html_str);

find()方法是以选择器的方式来获取其中的某一个元素的,返回的是一个数组。

$dom->find($selector);

其中有一个坑,我们一般会用var_dump()来打印一个对象。但是对于这个工具来说,这样是无法打印出我们想要的东西的。

这个HTML解析工具为我们封装了一个dump(),我只需要$dom->dump()就可以调试打印了。

其中有一个BASE_URL,这个是我定义的一个常量。

define('BASE_URL','http://music.163.com');

define('MV_BASE_URL','http://music.163.com/mv?id=');

define('SONG_BASE_URL','http://music.163.com/song?id=');

define('APP_PATH',__DIR__);

define('DATA_PATH',APP_PATH.'/data');

这些路径很容易看懂吧。接下来会用到。

通过上述的代码我们封装了好了方法,我们需要一个初始URL来驱动我们整个爬虫的运行。

$url = 'http://music.163.com/discover/artist/cat?id=1001&initial=65';

接下来我们调用我们的方法

$url = 'http://music.163.com/discover/artist/cat?id=1001&initial=65';

$html_str = sendRequest($url);

$arr = getSingerHomeUrl($html_str);

二.分析歌手页面源码

972054613890

图5

可以从图5看出,整个歌手的数据分为两个部分。

第一部分:带图片的。

第二部分:不带图片。

接下来我们需要做的就是分析这两者的源码。

972054613890

图6

我们分析两者的源码可以得到如下的结论。

1、a标签的herf的值包含“/artist?id=”

2、并且歌手的id为的值为数字

3、歌手的主页的url为 http://music.163.com/artist?id=xxxx

很容易得到下面的代码

/**

* 获取歌手的信息,通过解析HTML字符串

* @param $html_str HTML字符串

* @param null $type 歌手类型

* @return array

*/

public function getSingerInfo(){

$file_path = APP_PATH.'/all/singer.log';

if (!file_exists($file_path)){

touch($file_path);

}

$html_str = $this->sendRequest();

$typeUrlsMap = $this->getSingerHomeUrl($html_str);

foreach ($typeUrlsMap as $type

=>$urls){

foreach ($urls as $url){

//***这个段代码为什么这样写后续在进行讲解***

//***先立一个flag***

//-------------FLAG1--------------

$home_html_str = $this->sendRequest($url);

do{

$home_html_str = $this->sendRequest($url);

if (empty($home_html_str)) {

Log::addLog('账号被封,证在等待解封,当前的时间戳'.time(),Log::WARNING);

sleep(1);

}

}while(empty($home_html_str));

//-------------FLAG1--------------

Log::addLog('正常运行['.$type.']......',Log::WARNING);

$this->getSingerInfoByHomeHtml($home_html_str,$type,$url);

}

}

}

/**

* 获取歌手的信息,通过解析HTML字符串

* @param $html_str HTML字符串

* @param null $type 歌手类型

* @return array

*/

function getSingerInfoByHomeHtml($html_str,$type=null,$url=null){

$dom = HtmlDomParser::str_get_html($html_str);

$elems = $dom->find('li');

$rltSingerArr = [];

foreach ($elems as $key => $value){

$aas = $value->find('a');

foreach ($aas as $k=>$v){

//根据上面分析我们很容易得到每个歌手的首页地址

$id = trim(trim($v->href),"/artist?id=");

if (strpos(trim($v->href),'/artist?id=') !== false && is_numeric($id) && !empty($v->text())){

$name = $v->text();

$href = BASE_URL.trim($v->href);

$singerInfoArr = [$id,$name,'',$href,'1090',$type];

$rltStr = implode("\t",$singerInfoArr);

$rltSingerArr[] = $rltStr;

//将获取的歌手信息写入文件

file_put_contents(APP_PATH.'/all/singer.log',$rltStr.PHP_EOL,FILE_APPEND) && $this->getSongAndMvInfo($href,$id);

}

}

}

return $rltSingerArr;

}

//我们以同样的方法获取到每一首歌的URL构成

//拼接处每一首歌的URL,然后获取他的详细信息

//在上面的函数里

function getSongAndMvInfo($url=null,$id=null){

$html_str = null;

do{

$html_str = $this->sendRequest($url);

if (empty($html_str)) {

Log::addLog('请求歌手主页账号被封['.$url.'],waitting.....'.time(),Log::WARNING);

sleep(1);

}

}while(empty($html_str));

Log::addLog('歌手获取URL正常运行['.$url.']',Log::WARNING);

$dom = HtmlDomParser::str_get_html($html_str);

$elems = $dom->find('textarea');

$jsonArr = [];

$arr = [];

foreach ($elems as $elem_a){

if ($elem_a->style == "display:none;" && !$elem_a->has_child() && empty($elem_a->id) && empty($elem_a->name)){

$jsonStr = $elem_a->text();

$jsonArr = $this->jsonToArray($jsonStr);

$this->formatOutput($jsonArr,APP_PATH.'/all/song.log',$id);

}

}

}

//格式化输出

function formatOutput($jsonArr,$fileName,$id){

if (!file_exists($fileName)){

touch($fileName);

}

foreach ($jsonArr as $song){

$arr = [$song['id'],$song['name'],$id,'',SONG_BASE_URL.$song['id'],'1090','',MV_BASE_URL.$song['mvid']];

file_put_contents($fileName,implode("\t",$arr).PHP_EOL,FILE_APPEND) ;

}

}

function jsonToArray($json_str,$isObj=true){

return json_decode($json_str,$isObj);

}

解释一下FLAG1

在实际测试中,发现网易云会进行封IP,因为我们请求太频繁,但是在封了账号以后,会在在几秒以后解封账号。

总结一下:

1、分析网页构成。

2、设计字符获取算法。

后期优化

上面存在的问题

1、直接阻塞式的获取歌手和歌曲的信息

2、如果中间出现了问题,还要从头开始爬,无法从断点处继续爬。

针对上述的问题得出解决方案:

1、多进程异步处理。

先获取歌手信息,存入多个文件,或者数据库。然后主进程开启多个子进程进行处理。

2、设置异常函数句柄,在回调函数里面保存状态。

register_shutdown_function(callback $funcName)

还需要进一步完善。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值