热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

蚂蜂窝爬虫

Nodejs爬取蚂蜂窝文章的爬虫以及搭建第三方服务器如题,本项目用Nodejs实现了对蚂蜂窝网站的爬取,并将数据储存到MongoDB中,再

Nodejs爬取蚂蜂窝文章的爬虫以及搭建第三方服务器

如题,本项目用Nodejs实现了对蚂蜂窝网站的爬取,并将数据储存到MongoDB中,再以Express作服务器端,Angularjs作前端实现对数据的托管。
本项目Github地址:https://github.com/golmic/mafengwo-spider
本项目线上地址: http://mafengwo.lujq.me
本文介绍其中部分的技术细节。


获取数据

打开蚂蜂窝网站,发现文章部分的数据是用Ajax获取的,包括分页也是,所以查看一下实际的请求路径,为http://www.mafengwo.cn/ajax/ajax_article.php?start=1
所以程序应该向这个php文件发送请求,用Nodejs的话直接http请求也是没问题的,为了代码好看,我使用request库封装一下。


1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16

function getArticleList(pageNum) {
request({ url: "http://www.mafengwo.cn/ajax/ajax_article.php?start&#61;" &#43; pageNum, headers: { &#39;User-Agent&#39;: &#39;Mozilla/5.0&#39; } }, function(error, response, data) { var res &#61; data.match(/i\\\/\d{7}/g); for (var i &#61; 0; i <12; i&#43;&#43;) { articlesUrl[i] &#61; res[i * 3].substr(3, 7); }; async.each(articlesUrl, getArticle, function(err) { console.log(&#39;err: &#39; &#43; err); }); }); }

每页是12篇文章&#xff0c;每篇文字都是&#xff08;伪&#xff09;静态页面&#xff0c;正则提取出其中的文章页Url。
对每个Url发送请求&#xff0c;拿到源码。


1
2
3
4
5
6 7 8 9 10

function getArticle(urlNumber) {
request({ url: "http://www.mafengwo.cn/i/" &#43; urlNumber &#43; ".html", headers: { &#39;User-Agent&#39;: &#39;Mozilla/5.0&#39; } }, function(error, response, data) { //处理数据 }); };

接下来就是处理数据了。

这一段代码较长&#xff0c;但是目的是非常明确的&#xff0c;代码也很清晰。我们需要从这个页面中拿到文章的标题&#xff0c;以及文章的内容。&#xff08;文章作者以及发布时间由于时间关系我并没有处理&#xff0c;不过也在代码以及数据库种预留了位置&#xff0c;这个同理很容易完成。&#xff09;
来&#xff0c;我们分析一下这段代码。


1
2
3
4

var title, content, creator, created;
/*获取标题*/
title &#61; data.match(/\s*.&#43;\s*<\/h1>/).toString().replace(/\s*/g, "").replace(/$/g, "").replace(/\//g, "|").match(/>.&#43;title &#61; title.substring(1, title.length - 1);

先是正则获取标题&#xff0c;然后把标题中的特殊符号做一下处理。


1
2
3
4
5
6 7 8 9 10 11

/*如果有背景音乐就获取背景音乐*/
if (data.indexOf("music_url") &#39;music_auto_play&#39;)) { mp3url &#61; data.substring(data.indexOf("music_url"), data.indexOf(&#39;music_auto_play&#39;)); } else { mp3url &#61; data.substring(data.indexOf("music_url"), data.indexOf(&#39;is_new_note&#39;)); }; mp3url &#61; mp3url.match(/http.&#43;\.mp3/); if (mp3url) { mp3url &#61; mp3url.toString(); content &#61; &#39;&#39;; };

然后在实际访问蚂蜂窝网站时发现大多数文章都配有背景音乐&#xff0c;那我也给加上好了。于是这一段代码负责了获取背景音乐的直链地址。


1
2
3
4
5
6

/*获取文章内容&#xff0c;发现有两种类型&#xff0c;分别适配*/
if (data.indexOf(&#39;a_con_text cont&#39;) !&#61; -1) { content &#43;&#61; data.substring(data.indexOf("a_con_text cont") &#43; 296, data.indexOf(&#39;integral&#39;) - 12); } else { content &#43;&#61; data.substring(data.indexOf("ginfo_kw_hotel") &#43; 16, data.indexOf(&#39;vc_total&#39;) - 19); };

获取文章内容&#xff0c;在写这段代码时发现它的文章是有两种dom结构的&#xff0c;所以分类处理了一下。


1
2
3
4
5
6

/*移除它给图片定义的父标签宽度以便响应式*/
content &#61; content.replace(/width:\d*px/g, "");
/*把文中˚∫圖片作為在列表中顯示時的圖片*/ /*有的第一張圖片是表情.....處理一下..*/ imageUrl &#61; data.match(/http.*\.(jpeg|png|jpg)"/).toString(); imageUrl &#61; imageUrl.substring(0, imageUrl.indexOf(&#39;"&#39;));

这一段代码处理一下图片&#xff0c;第一是文中的图片因为蚂蜂窝给定义了好多样式&#xff0c;并不符合响应式规则&#xff0c;我把与响应式冲突的部分给处理了一下。
然后为了美观&#xff0c;把文章的第一张图片作为列表显示时的特色图片&#xff0c;记录一下Url。


储存数据

事实上整个的任务到此就可以结束了。


1
2
3
4

fs.writeFile("html/" &#43; title &#43; ".html", content, function(e) { if (e) throw e; console.log(title); });

把每篇文章作为一个静态文件保存。然后遍历一下目录得到文章列表&#xff0c;凭借Nginx对静态资源强大的处理能力&#xff0c;这个网站也算是可以完工了。
出于后期管理文档以及把项目做得高大上点的目的&#xff0c;还是采用NOsql的翘楚MongoDB作为数据库端的解决方案。


1
2
3
4
5
6

MongoClient.connect(&#39;mongodb://localhost:27017/mean&#39;, function(err, db) {
assert.equal(null, err); insertArticle(db, title, content, creator, mp3url, imageUrl, created, function() { db.close(); }); });

把数据储存到mean数据库中&#xff0c;mean即MongoDB/Expressjs/Angularjs/Nodejs的js全栈实践。
这样数据的储存就完成了。


搭建服务器


目录结构

为了后期维护以及合作开发&#xff0c;服务器端目录的结构与命名规则也需要注意下。
目录结构


数据结构

为了后期管理员以及作者维护文章的考虑&#xff0c;数据库中不止有Articles一个collection&#xff0c;还有一个users的collection。
结构分别如下&#xff1a;
文章&#xff1a;


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

var ArticleSchema &#61; new Schema({
created: {
type: Date, default: Date.now }, title: { type: String, default: &#39;&#39;, trim: true, required: &#39;Title cannot be blank&#39; }, content: { type: String, default: &#39;&#39;, trim: true }, mp3url:{ type:String }, imageUrl:{ type:String }, creator: { type: String, default: &#39;golmic&#39;, } });

用户&#xff1a;


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

var UserSchema &#61; new Schema({
firstName: String,
lastName: String, email: { type: String, // Validate the email format match: [/.&#43;\&#64;.&#43;\..&#43;/, "Please fill a valid email address"] }, username: { type: String, // Set a unique &#39;username&#39; index unique: true, // Validate &#39;username&#39; value existance required: &#39;Username is required&#39;, // Trim the &#39;username&#39; field trim: true }, password: { type: String, // Validate the &#39;password&#39; value length validate: [ function(password) { return password && password.length > 6; }, &#39;Password should be longer&#39; ] }, salt: { type: String }, provider: { type: String, // Validate &#39;provider&#39; value existance required: &#39;Provider is required&#39; }, providerId: String, providerData: {}, created: { type: Date, // Create a default &#39;created&#39; value default: Date.now } });

Nodejs驱动下&#xff0c;很容易实现对文章以及用户的CRUD操作。这里只展示了对文章操作的代码。


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

exports.list &#61; function(req, res) {
Article.find().sort(&#39;-created&#39;).exec(function(err, articles) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { for(var i in articles){ articles[i].content&#61;&#39;&#39;; }; res.json(articles); } }); }; exports.read &#61; function(req, res) { res.json(req.article); }; exports.update &#61; function(req, res) { var article &#61; req.article; article.title &#61; req.body.title; article.content &#61; req.body.content; article.save(function(err) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { res.json(article); } }); }; exports.delete &#61; function(req, res) { var article &#61; req.article; article.remove(function(err) { if (err) { return res.status(400).send({ message: getErrorMessage(err) }); } else { res.json(article); } }); };


路由规则

首页为文章列表&#xff0c;然后每篇文章有一个url。前端规则很容易&#xff0c;另外为了符合RESTful API的要求&#xff0c;后端需要提供对CRUD操作的API。文章部分路由规则如下&#xff1a;


1
2
3
4
5
6 7 8 9 10

module.exports &#61; function(app) {
app.route(&#39;/api/articles&#39;) .get(articles.list) .post(users.requiresLogin, articles.create); app.route(&#39;/api/articles/:articleId&#39;) .get(articles.read) .put(users.requiresLogin, articles.hasAuthorization, articles.update) .delete(users.requiresLogin, articles.hasAuthorization, articles.delete); app.param(&#39;articleId&#39;, articles.articleByID); };

用户部分同理.

前端路由由Angular控制&#xff1a;


1
2
3
4
5
6 7 8 9 10 11 12 13 14 15 16 17

angular.module(&#39;articles&#39;).config([&#39;$routeProvider&#39;,
function($routeProvider) { $routeProvider. when(&#39;/&#39;, { templateUrl: &#39;articles/views/list-articles.client.view.html&#39; }). when(&#39;/articles/create&#39;, { templateUrl: &#39;articles/views/create-article.client.view.html&#39; }). when(&#39;/articles/:articleId&#39;, { templateUrl: &#39;articles/views/view-article.client.view.html&#39; }). when(&#39;/articles/:articleId/edit&#39;, { templateUrl: &#39;articles/views/edit-article.client.view.html&#39; }); } ]);

前端用ngResource模块处理资源位置&#xff1a;


1
2
3
4
5
6 7 8 9 10

angular.module(&#39;articles&#39;).factory(&#39;Articles&#39;, [&#39;$resource&#39;, function($resource) { // Use the &#39;$resource&#39; service to return an article &#39;$resource&#39; object return $resource(&#39;api/articles/:articleId&#39;, { articleId: &#39;&#64;_id&#39; }, { update: { method: &#39;PUT&#39; } }); }]);


用户管理

文章作者以及管理员可以修改或者删除文章&#xff0c;逻辑代码见路由部分&#xff0c;实现代码见CRUD部分。


最终效果



其它

其它未尽技术细节请发issue或邮件交流。

 

全能程序员交流QQ群290551701&#xff0c;群内程序员都是来自&#xff0c;百度、阿里、京东、小米、去哪儿、饿了吗、蓝港等高级程序员 &#xff0c;拥有丰富的经验。加入我们&#xff0c;直线沟通技术大牛&#xff0c;最佳的学习环境&#xff0c;了解业内的一手的资讯。如果你想结实大牛&#xff0c;那 就加入进来&#xff0c;让大牛带你超神&#xff01;

 


推荐阅读
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 微信小程序:授权登录与手机号绑定
    本文详细介绍了微信小程序中用户授权登录及绑定手机号的流程,结合官方指引和实际开发经验,提供了一套完整的实现方案,帮助开发者更好地理解和应用。 ... [详细]
  • 本文将介绍如何利用Python爬虫技术抓取国内主流在线学习平台的数据,并以51CTO学院为例,进行详细的技术解析和实践操作。 ... [详细]
  • 深入分析十大PHP开发框架
    随着PHP技术的发展,各类开发框架层出不穷,成为了开发者们热议的话题。本文将详细介绍并对比十款主流的PHP开发框架,旨在帮助开发者根据自身需求选择最合适的工具。 ... [详细]
  • 本文介绍了如何在多线程环境中实现异步任务的事务控制,确保任务执行的一致性和可靠性。通过使用计数器和异常标记字段,系统能够准确判断所有异步线程的执行结果,并根据结果决定是否回滚或提交事务。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
  • This request pertains to exporting the hosted_zone_id attribute associated with the aws_rds_cluster resource in Terraform configurations. The absence of this attribute can lead to issues when integrating DNS records with Route 53. ... [详细]
  • 本文探讨了如何通过预处理器开关选择不同的类实现,并解决在特定情况下遇到的链接器错误。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 本文探讨了为何相同的HTTP请求在两台不同操作系统(Windows与Ubuntu)的机器上会分别返回200 OK和429 Too Many Requests的状态码。我们将分析代码、环境差异及可能的影响因素。 ... [详细]
  • 深入解析ESFramework中的AgileTcp组件
    本文详细介绍了ESFramework框架中AgileTcp组件的设计与实现。AgileTcp是ESFramework提供的ITcp接口的高效实现,旨在优化TCP通信的性能和结构清晰度。 ... [详细]
  • Python3 中使用 lxml 模块解析 XPath 数据详解
    XPath 是一种用于在 XML 文档中查找信息的路径语言,同样适用于 HTML 文件的搜索。本文将详细介绍如何利用 Python 的 lxml 模块通过 XPath 技术高效地解析和抓取网页数据。 ... [详细]
  • 本文探讨了2019年前端技术的发展趋势,包括工具化、配置化和泛前端化等方面,并提供了详细的学习路线和职业规划建议。 ... [详细]
  • 1、字符型常量字符型常量指单个字符,是用一对单引号及其所括起来的字符表示。例如:‘A’、‘a’、‘0’、’$‘等都是字符型常量。C语言的字符使用的就是 ... [详细]
  • 本文详细介绍了 Python 中的 with 语句及其背后的上下文管理器机制,从基本概念入手,通过具体示例和原理分析,帮助读者深入理解这一重要的资源管理工具。 ... [详细]
author-avatar
天使哥特式
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有