自从使用hexo在github page更新博客之后,我每次在cnblog上发布文章,需要手动再更新hexo。hexo使用markdown格式来写文章,手动更新需要对文章本身内容进行转化,做成md文件再进行上传,后来就想到,本身爬虫就可以对页面中的各种元素进行提取,同时markdown使用的是标记语法,那么使用爬虫分析文章元素,提取主要内容并且根据模板自动生成对应的md文件理论上是可行的。
由于我的hexo博客使用的是默认布局,所以在hexo目录下直接执行:
hexo new model
这样就生成了一个model.md文件,接下来就是把这个文件改装成所需的模板,打开model.md,能看到默认布局hexo文章只有一个简单的front-matter区域用来进行文章变量的指定:
---
title: model
date: 2018-11-22 09:49:22
tags:
---
按照官方文档,front-matter用于文章变量的指定,本身不会作为markdown被解析,所以模板布局可以分为两个部分:
1--- 2{{front-matter}} 3--- 4{{markdown}}
接下来就是对文章元素的提取并且转化成相应的hexo文章内容,并且拼接填充至模板当中了。
这里我简单封装了一个MarkdownGenerator类用来把文章转换成markdown文件:
1namespace Root; 2 3use GuzzleHttp\Client; 4use Symfony\Component\DomCrawler\Crawler; 5 6Class MarkdownGenerator 7{ 8 private $client; 9 10 private $crawler; 11 12 private $url; 13 14 private $assetPath; 15 16 private $contentsArray = []; 17 18 private $categories = []; 19 20 private $tags = []; 21 22 private $title; 23 24 private $dateString; 25 26 private $documentName; 27 28 /** 初始化,会在同目录下生成一个和文档同名的文件夹用来装静态资源 29 * MarkdownGenerator constructor. 30 * @param $documentName 31 */ 32 public function __construct($documentName) 33 { 34 $this->documentName = $documentName; 35 $this->assetPath = __DIR__ . "/{$this->documentName}/"; 36 if(!is_dir($this->assetPath)){ 37 mkdir($this->assetPath, 755); 38 } 39 $headers = [ 40 'user-agent' => 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36', 41 ]; 42 $this->client = new Client([ 43 'timeout' => 20, 44 'headers' => $headers 45 ]); 46 $this->crawler = new Crawler(); 47 } 48 49 /** 设置文章url 50 * @param string $url 51 */ 52 public function setUrl($url = '') 53 { 54 $this->url = $url; 55 } 56 57 /** 设置文章tags 58 * @param array $tags 59 */ 60 public function setTags($tags = []) 61 { 62 $this->tags = $tags; 63 } 64 65 /** 设置文章categories 66 * @param array $categories 67 */ 68 public function setCategories($categories = []) 69 { 70 $this->categories = $categories; 71 } 72 73 /** 74 * 生成markdown文件 75 */ 76 public function generate() 77 { 78 $responseContent = $this->client->request('GET', $this->url)->getBody()->getContents(); 79 $this->crawler->addHtmlContent($responseContent); 80 try{ 81 //获取文章title 82 $this->title = trim($this->crawler->filterXPath('//h1[@class="postTitle"]')->text()); 83 //获取文章发布时间 84 $this->dateString = trim($this->crawler->filterXPath('//span[@id="post-date"]')->text()); 85 //处理文章正文部分 86 $this->crawler->filterXPath('//div[@id="cnblogs_post_body"]')->children()->each(function(Crawler $node) { 87 $this->contentsArray[] = $this->parseParagraph($node); 88 }); 89 //组装markdown正文部分 90 $markdownContent = $this->makeContent(); 91 $frontMatter = $this->makeFrontMatter(); 92 $tmplate = file_get_contents('model.md'); 93 $content = str_replace(['{{front-matter}}','{{markdown}}'],[$frontMatter, $markdownContent], $tmplate); 94 file_put_contents($this->documentName.'.md', $content); 95 }catch (\Throwable $e){ 96 print_r($e->getMessage()); 97 } 98 } 99 100 /** 处理段落 101 * @param Crawler $node 102 * @return mixed|string 103 */ 104 private function parseParagraph(Crawler $node) 105 { 106 $res = $node->html(); 107 //替换a标签,替换成markdown当中的格式 108 $linkPattern = '#<a\b[^>]+\bhref=\"([^\"]*)\"[^>]*>([\s\S]*?)<\/a>#'; 109 preg_match_all($linkPattern, $res, $links); 110 if(!empty($links[0])){ 111 foreach ($links[2] as $k => $link){ 112 $l = "[{$link}]({$links[1][$k]})"; 113 $res = str_replace($links[0][$k], $l, $res); 114 } 115 } 116 117 //处理图片,图片这里使用hexo的图片标签,格式为{% asset_img name.format alt %}, 对应图片放在和post目录下和md文件同名文件夹中 118 $imgPattern = '#<img\b[^>]+\bsrc="([^"]*)"[^>]+\balt="([^"]*)"[^>]*>#'; 119 preg_match_all($imgPattern, $res, $imgs); 120 if(!empty($imgs[0])){ 121 foreach ($imgs[2] as $k => $img){ 122 $imageUrl = $imgs[1][$k]; 123 //下载图片并放入对应的文件夹内 124 $imageName = pathinfo($imageUrl)['basename']; 125 $fileName = $this->assetPath . $imageName; 126 $image = $this->client->get($imageUrl)->getBody()->getContents(); 127 file_put_contents($fileName, $image); 128 $i = "{% asset_img {$imageName} {$imgs[2][$k]} %}"; 129 $res = str_replace($imgs[0][$k], $i, $res); 130 } 131 } 132 //处理内嵌代码 133 if($node->attr('class') === 'cnblogs_code'){ 134 $plainCodes = trim($node->text()); 135 $res = htmlspecialchars("```") ."\n{$plainCodes}\n" . htmlspecialchars("```"); 136 } 137 return $res; 138 } 139 140 /** 组装markdown正文部分 141 * @return string 142 */ 143 private function makeContent() 144 { 145 return (implode($this->contentsArray, "\n\n")); 146 } 147 148 /** 组装frontMatter部分 149 * @return string 150 */ 151 private function makeFrontMatter() 152 { 153 $res = <<<FM 154title: {$this->title} 155date: {$this->dateString} 156FM; 157 if(!empty($this->categories)){ 158 $res .= "\ncategories:\n- " . implode($this->categories, "\n- "); 159 } 160 if(!empty($this->tags)){ 161 $res .= "\ntags:\n- " . implode($this->tags, "\n- "); 162 } 163 return $res; 164 } 165}
接下来只用实例化generator类,然后设置各项属性,调用generate方法就能够抓取生成markdown文件了:
1$generator = new MarkdownGenerator('test'); 2$generator->setUrl('https://www.cnblogs.com/jackiebao/p/8466232.html'); 3$generator->setTags(['test1','test2']); 4$generator->setCategories(['test_cat']); 5$generator->generate();