摘要:爬蟲抓取問答一需求概述抓取中國領(lǐng)先的開發(fā)者社區(qū)網(wǎng)站上問答及標(biāo)簽數(shù)據(jù)側(cè)面反映最新的技術(shù)潮流以及國內(nèi)程序猿的關(guān)注焦點(diǎn)注抓取腳本純屬個人技術(shù)鍛煉非做任何商業(yè)用途二開發(fā)環(huán)境及包依賴運(yùn)行環(huán)境依賴三流程與實(shí)踐首先先設(shè)計兩張表文章發(fā)布用戶文章標(biāo)題瀏覽
PHP爬蟲抓取segmentfault問答 一 需求概述
抓取中國領(lǐng)先的開發(fā)者社區(qū)segment.com網(wǎng)站上問答及標(biāo)簽數(shù)據(jù),側(cè)面反映最新的技術(shù)潮流以及國內(nèi)程序猿的關(guān)注焦點(diǎn).
二 開發(fā)環(huán)境及包依賴注:抓取腳本純屬個人技術(shù)鍛煉,非做任何商業(yè)用途.
運(yùn)行環(huán)境
CentOS Linux release 7.0.1406 (Core)
PHP7.0.2
Redis3.0.5
Mysql5.5.46
Composer1.0-dev
composer依賴
symfony/dom-crawler
三 流程與實(shí)踐首先,先設(shè)計兩張表:post,post_tag
CREATE TABLE `post` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT "pk", `post_id` varchar(32) NOT NULL COMMENT "文章id", `author` varchar(64) NOT NULL COMMENT "發(fā)布用戶", `title` varchar(512) NOT NULL COMMENT "文章標(biāo)題", `view_num` int(11) NOT NULL COMMENT "瀏覽次數(shù)", `reply_num` int(11) NOT NULL COMMENT "回復(fù)次數(shù)", `collect_num` int(11) NOT NULL COMMENT "收藏次數(shù)", `tag_num` int(11) NOT NULL COMMENT "標(biāo)簽個數(shù)", `vote_num` int(11) NOT NULL COMMENT "投票次數(shù)", `post_time` date NOT NULL COMMENT "發(fā)布日期", `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT "抓取時間", PRIMARY KEY (`id`), KEY `idx_post_id` (`post_id`) ) ENGINE=MyISAM AUTO_INCREMENT=7108 DEFAULT CHARSET=utf8 COMMENT="帖子";
CREATE TABLE `post_tag` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT "PK", `post_id` varchar(32) NOT NULL COMMENT "帖子ID", `tag_name` varchar(128) NOT NULL COMMENT "標(biāo)簽名稱", PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=15349 DEFAULT CHARSET=utf8 COMMENT="帖子-標(biāo)簽關(guān)聯(lián)表";
當(dāng)然有同學(xué)說,這么設(shè)計不對,標(biāo)簽是個獨(dú)立的主體,應(yīng)該設(shè)計post,tag,post_tag三張表,文檔和標(biāo)簽之間再建立聯(lián)系,這樣不僅清晰明了,而且查詢也很方便.
這里簡單處理是因?yàn)槭紫炔皇呛苷降拈_發(fā)需求,自娛自樂,越簡單搞起來越快,另外三張表抓取入庫時就要多一張表,更重要的判斷標(biāo)簽重復(fù)性,導(dǎo)致抓取速度減慢.
整個項(xiàng)目工程文件如下:
app/config/config.php /*配置文件*/ app/helper/Db.php /*入庫腳本*/ app/helper/Redis.php /*緩存服務(wù)*/ app/helper/Spider.php /*抓取解析服務(wù)*/ app/helper/Util.php /*工具*/ app/vendor/composer/ /*composer自動加載*/ app/vendor/symfony/ /*第三方抓取服務(wù)包*/ app/vendor/autoload.php /*自動加載*/ app/composer.json /*項(xiàng)目配置*/ app/composer.lock /*項(xiàng)目配置*/ app/run.php /*入口腳本*/
因?yàn)楣δ芎芎唵?所以沒有必要引用第三方開源的PHP框架
基本配置
class Config { public static $spider = [ "base_url" => "http://segmentfault.com/questions?", "from_page" => 1, "timeout" => 5, ]; public static $redis = [ "host" => "127.0.0.1", "port" => 10000, "timeout" => 5, ]; public static $mysql = [ "host" => "127.0.0.1", "port" => "3306", "dbname" => "segmentfault", "dbuser" => "user", "dbpwd" => "user", "charset" => "utf8", ]; }
curl抓取頁面的函數(shù)
public function getUrlContent($url) { if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) { return false; } $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); curl_setopt($curl, CURLOPT_TIMEOUT, Config::$spider["timeout"]); curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.111 Safari/537.36"); $content = curl_exec($curl); curl_close($curl); return $content; }
這里要有兩點(diǎn)要注意:
第一,要開啟CURLOPT_FOLLOWLOCATION301跟蹤抓取,因?yàn)閟egmentfautl官方會做域名跳轉(zhuǎn),比如http://www.segmentfault.com/會跳轉(zhuǎn)到到"http://segmentfault.com"等等.
第二,指定UserAgent,否則會出現(xiàn)301重定向到瀏覽器升級頁面.
crawler解析處理
public function craw() { $content = $this->getUrlContent($this->getUrl()); $crawler = new Crawler(); $crawler->addHtmlContent($content); $found = $crawler->filter(".stream-list__item"); //判斷是否頁面已經(jīng)結(jié)束 if ($found->count()) { $data = $found->each(function (Crawler $node, $i) { //問答ID $href = trim($node->filter(".author li a")->eq(1)->attr("href")); $a = explode("/", $href); $post_id = isset($a[2]) ? $a[2] : 0; //檢查該問答是否已經(jīng)抓取過 if ($post_id == 0 || !(new Redis())->checkPostExists($post_id)) { return $this->getPostData($node, $post_id, $href); } return false; }); //去除空的數(shù)據(jù) foreach ($data as $i => $v) { if (!$v) { unset($data[$i]); } } $data = array_values($data); $this->incrementPage(); $continue = true; } else { $data = []; $continue = false; } return [$data, $continue]; } private function getPostData(Crawler $node, $post_id, $href) { $tmp = []; $tmp["post_id"] = $post_id; //標(biāo)題 $tmp["title"] = trim($node->filter(".summary h2.title a")->text()); //回答數(shù) $tmp["reply_num"] = intval(trim($node->filter(".qa-rank .answers")->text())); //瀏覽數(shù) $tmp["view_num"] = intval(trim($node->filter(".qa-rank .views")->text())); //投票數(shù) $tmp["vote_num"] = intval(trim($node->filter(".qa-rank .votes")->text())); //發(fā)布者 $tmp["author"] = trim($node->filter(".author li a")->eq(0)->text()); //發(fā)布時間 $origin_time = trim($node->filter(".author li a")->eq(1)->text()); if (mb_substr($origin_time, -2, 2, "utf-8") == "提問") { $tmp["post_time"] = Util::parseDate($origin_time); } else { $tmp["post_time"] = Util::parseDate($this->getPostDateByDetail($href)); } //收藏數(shù) $collect = $node->filter(".author .pull-right"); if ($collect->count()) { $tmp["collect_num"] = intval(trim($collect->text())); } else { $tmp["collect_num"] = 0; } $tmp["tags"] = []; //標(biāo)簽列表 $tags = $node->filter(".taglist--inline"); if ($tags->count()) { $tmp["tags"] = $tags->filter(".tagPopup")->each(function (Crawler $node, $i) { return $node->filter(".tag")->text(); }); } $tmp["tag_num"] = count($tmp["tags"]); return $tmp; }
通過crawler將抓取的列表解析成待入庫的二維數(shù)據(jù),每次抓完,分頁參數(shù)遞增.
這里要注意幾點(diǎn):
1.有些問答已經(jīng)抓取過了,入庫時需要排除,因此此處加入了redis緩存判斷.
2.問答的創(chuàng)建時間需要根據(jù)"提問","解答","更新"狀態(tài)來動態(tài)解析.
3.需要把類似"5分鐘前","12小時前","3天前"解析成標(biāo)準(zhǔn)的Y-m-d格式
入庫操作
public function multiInsert($post) { if (!$post || !is_array($post)) { return false; } $this->beginTransaction(); try { //問答入庫 if (!$this->multiInsertPost($post)) { throw new Exception("failed(insert post)"); } //標(biāo)簽入庫 if (!$this->multiInsertTag($post)) { throw new Exception("failed(insert tag)"); } $this->commit(); $this->pushPostIdToCache($post); $ret = true; } catch (Exception $e) { $this->rollBack(); $ret = false; } return $ret; }
采用事務(wù)+批量方式的一次提交入庫,入庫完成后將post_id加入redis緩存
啟動作業(yè)
require "./vendor/autoload.php"; use helperSpider; use helperDb; $spider = new Spider(); while (true) { echo "crawling from page:" . $spider->getUrl() . PHP_EOL; list($data, $ret) = $data = $spider->craw(); if ($data) { $ret = (new Db)->multiInsert($data); echo count($data) . " new post crawled " . ($ret ? "success" : "failed") . PHP_EOL; } else { echo "no new post crawled".PHP_EOL; } echo PHP_EOL; if (!$ret) { exit("work done"); } };
運(yùn)用while無限循環(huán)的方式執(zhí)行抓取,遇到抓取失敗時,自動退出,中途可以按Ctrl + C中斷執(zhí)行.
四 效果展示抓取執(zhí)行中
問答截圖
標(biāo)簽截圖
以上的設(shè)計思路和腳本基本上可以完成簡單的抓取和統(tǒng)計分析任務(wù)了.
我們先看下TOP25標(biāo)簽統(tǒng)計結(jié)果:
可以看出segmentfault站點(diǎn)里,討論最熱的前三名是javascript,php,java,而且前25個標(biāo)簽里跟前端相關(guān)的(這里不包含移動APP端)居然有13個,占比50%以上了.
每月標(biāo)簽統(tǒng)計一次標(biāo)簽,就可以很方便的掌握最新的技術(shù)潮流,哪些技術(shù)的關(guān)注度有所下降,又有哪些在上升.
有待完善或不足之處
1.單進(jìn)程抓取,速度有些慢,如果開啟多進(jìn)程的,則需要考慮進(jìn)程間避免重復(fù)抓取的問題
2.暫不支持增量更新,每次抓取到從配置項(xiàng)的指定頁碼開始一直到結(jié)束,可以根據(jù)已抓取的post_id做終止判斷(post_id雖不是連續(xù)自增,但是一直遞增的)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/21348.html
摘要:學(xué)了天的,寫了一個爬蟲開源項(xiàng)目。現(xiàn)在把所有的筆記放到記錄下來,算是一個紀(jì)念。定義抓取下載的檔案對目標(biāo)檔案建立一個網(wǎng)絡(luò)連接。 學(xué)了7天的PHP/CURL,寫了一個爬蟲開源項(xiàng)目。 現(xiàn)在把所有的筆記放到Segmentfault記錄下來,算是一個紀(jì)念。 https://github.com/hosinoruri/Omoikane $target=http://www.WebbotsSp...
摘要:比如分鐘破譯朋友圈測試小游戲文章里用的方法但有些根本就沒有提供網(wǎng)頁端,比如今年火得不行的抖音。所以常用的方式就是通過在電腦上裝一些抓包軟件,將手機(jī)上的網(wǎng)絡(luò)請求全部顯示出來??偨Y(jié)下,重點(diǎn)是的抓取,關(guān)鍵是配置代理證書,難點(diǎn)是對請求的分析。 爬蟲的案例我們已講得太多。不過幾乎都是 網(wǎng)頁爬蟲 。即使有些手機(jī)才能訪問的網(wǎng)站,我們也可以通過 Chrome 開發(fā)者工具 的 手機(jī)模擬 功能來訪問,以便...
摘要:組件引擎負(fù)責(zé)控制數(shù)據(jù)流在系統(tǒng)中所有組件中流動,并在相應(yīng)動作發(fā)生時觸發(fā)事件。下載器下載器負(fù)責(zé)獲取頁面數(shù)據(jù)并提供給引擎,而后提供給。下載器中間件下載器中間件是在引擎及下載器之間的特定鉤子,處理傳遞給引擎的。 Scrapy 是用Python實(shí)現(xiàn)一個為爬取網(wǎng)站數(shù)據(jù)、提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架。 一、Scrapy框架簡介 Scrapy是一個為了爬取網(wǎng)站數(shù)據(jù),提取結(jié)構(gòu)性數(shù)據(jù)而編寫的應(yīng)用框架。 ...
閱讀 1792·2021-10-11 10:57
閱讀 2398·2021-10-08 10:14
閱讀 3424·2019-08-29 17:26
閱讀 3396·2019-08-28 17:54
閱讀 3050·2019-08-26 13:38
閱讀 2934·2019-08-26 12:19
閱讀 3636·2019-08-23 18:05
閱讀 1306·2019-08-23 17:04