背景
这个是这学期的作业,要用半学期的时间学习JavaScript然后写一个爬虫出来。但是之前并没有什么基础,只会用C写一些很简单的东西,也是头一次接触这么高档的语言,所以有些无所适从。但好在现成的代码够多,所以东拼西凑出来一个十分简陋的爬虫程序。其中很多的代码都是从老师的示例中搬运来的,最终成为了代码的搬运工,不过好在是能正常运行。
而且在学习过程中有了很多的感悟,想到和学到了很多的知识。所以才有了这么一篇废话很多的文章。主要是题外话有点多。
(;′⌒`)
然后这篇文章里面的部分东西也参考了同班的小段同学的研究成果,在此表示感谢ヽ(✿゚▽゚)ノ
爬取几个网站
针对不同的三个网站写了各自的爬虫,分别是雪球财经、中国新闻网还有同花顺关于期货的页面。
雪球网
概述
选取这个网站是因为它的网页结构比较简单,比较好欺负😕,而且这个网站内容比较有意思,相比于很多财经网站上贴的都是机构的研报,这个网站更多是大家的讨论,而且这里的老哥们说话都很有意思,有自己独特的见解,所以很喜欢这个网站。众所周知,在瞬息万变,风起云涌,白衣苍狗的中国投资市场,机构的研报很多都是忽悠人的,有真知灼见的往往是广大的股民朋友,所以才显得这个网站尤为可贵。
爬虫制作流程
最开始的问题是如何发出一个正确的请求。用到的是叫做request的第三方包。然后要注意的是第三方包的安装位置要和我们的程序在同一个文件夹,不然还是不能用。所以安装之前要在命令行中定位到我们的文件夹。最开始的时候不懂这个,折腾了老半天包也安装不上。
发出请求的代码大概是这个样子
var myRequest = require('request');
function request(url, callback) {
var options = {
url: url,
encoding: null,
//proxy: 'http://x.x.x.x:8080',
headers: headers,
timeout: 10000 //
}
myRequest(options, callback)
};
var headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}
大概就是这么些代码,其实是老师写的,我看得似懂非懂,大概知道如何工作,具体的也不敢问。看群里好多同学都把这些掌握的很好,像我这种成绩不好胆子又小的自然不敢说话。
反正是能用,也没有纠结那么多了。
好像听老师说这个发请求很重要的一点是利用headers把我们的请求伪装成正常的请求,不然网站可能屏蔽这样的请求。
之后我们要观察这个网站的源代码是个什么结构
按F12然后右键点击检查,就可以找到一个东西对应源代码里面什么地方。这也是最近的操作,最开始我学了很多天后才知道。
观察发现新闻的链接都是存在class叫做AnonymousHome_a__placeholder_3RZ的这么一个框框里面的,所以我们可以利用这个来定位新闻链接。
(其实叫新闻都不准确,因为这个网站都是大家发出来的讨论,或许应该叫做贴子)
放大了看还可以发现,这里的链接是没有写全的,只写了后半,所以在爬虫操作的时候还有把前半段给补上。就是加上"https://xueqiu.com"这么一小段才是完整的链接。
要实现这个功能,要用到一个叫做cheerio的包,利用里面的选择器,就是$对应的功能把这些链接给找出来。大概是这些代码。
和jQuery不一样的是cheerio在用之前要先把网页载入进去,就是用load方法。
之后是用each进行遍历,把每个符合条件的东西都找出来,然后再加上链接的前半段变成我们要的。
var myCheerio = require('cheerio');
seedget();
function seedget() {
request('https://xueqiu.com/', function(err, res, body) {
var html = myIconv.decode(body, myEncoding);
var $ = myCheerio.load(html, { decodeEntities: true });
var seedurl_news;
try {
seedurl_news = eval($(".AnonymousHome_a__placeholder_3RZ"));
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
seedurl_news.each(function(i, e) {
var myURL = "";
var href="";
try {
href=$(e).attr("href");
console.log(href);
myURL='https://xueqiu.com'+href
} catch (e) { console.log('识别种子页面中的新闻链接出错:' + e) }
newsget(myURL)
});
});
}
再然后就是对各个链接都访问一次,找出里面的各种信息。对应的是newsget这个函数。
然后由于我才疏学浅,很多东西都不会,所以只找了地址、标题、概述这三个信息。本来想找一下发布日期的,这个网站显示日期的方式是像“10分钟前”这样的,就没有多大意义。如何把这种格式的时间转化成能看的时间又是我不会的东西。而且作业很快就要交了,就没有办法再更多完善。
后面的代码是这样的
function newsget(myURL){
request(myURL, function(err, res, body){
var html_news = myIconv.decode(body, myEncoding);
var $ = myCheerio.load(html_news, { decodeEntities: true });
myhtml = html_news;
console.log("转码读取成功:" + myURL);
var fetch = {};
fetch.title = "";
fetch.content = "";
fetch.url = myURL;
fetch.title=$("title").text();
fetch.content=$('meta[name="description"]').attr("content");
var fetchadd ='INSERT INTO fetches(url,title,content )VALUES(?,?,?)';
var fetchadd_params=[fetch.url,fetch.title,fetch.content];
mysql.query(fetchadd,fetchadd_params,function(qerr,vals,fields){});
});
}
就是同样的操作,只不过要找的东西不同,而且这个网站的代码也比较有规律,很容易可以找到想要的东西。
最后是把这些爬取到的数据存到MySQL数据库里面,基本就整完了。
搜索功能
爬取到了数据,然后还有的一步是做一个前端,让我们可以通过网页查找数据。
var express = require('express');
var mysql = require('./mysql.js')
var app = express();
//app.use(express.static('public'));
app.get('/7.03.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.03.html");
})
app.get('/7.04.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.04.html");
})
app.get('/process_get', function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); //设置res编码为utf-8
//sql字符串和参数
var fetchSql = "select url,source_name,title,author,publish_date from fetches where title like '%" +
req.query.title + "%'";
mysql.query(fetchSql, function(err, result, fields) {
console.log(result);
res.end(JSON.stringify(result));
});
})
var server = app.listen(8080, function() {
console.log("访问地址为 http://127.0.0.1:8080/7.03.html")
})
前端代码就是这样了,同样是老师写的,我看的晕乎乎的,也不知道具体是怎么工作的。感觉很厉害的样子。o(=•ェ•=)m
然后我们打开网页,输入要查找的东西
点击submit
就可以返回这么一些东西。虽然有些凌乱,但仍能看出是什么意思,而且能根据标题找到相关网页链接,那么我们的目的就达到了。
完结撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。
尾声
如果我们在MySQL里面查看完整的爬取到的表格,会发现它非常的短,只有三十条信息
曾观摩一位前辈做关于知乎的爬虫,也遇到了类似的问题。大概的成因是网站用了Ajax技术,网页是分批次加载的,反应到实际使用中就是把滚动条拉到最下面才会又加载新的内容。然而这么简陋的爬虫是做不到那些复杂的事情的,所以爬取到的只有最开始给我们看的几条信息。
这是此次开发爬虫的一点遗憾。
同花顺期货网
这次老师要我们多做几个网站的爬虫。然后做着做着就发现,只要我的爬虫足够简陋,可移植性就会比较好。
然后需要修改的地方有二:中文编码方式和链接获取方式。
这个网站用的是gkb编码的,所以就用gkb来做。然后这次新闻链接是放在class=news-link的框框里面的,所以就找它来获取链接。相比于上一个代码,就做这两个小修改即可。
var myEncoding = "utf-8";
改为
var myEncoding = "gbk";
try { seedurl_news = eval($(".AnonymousHome_a__placeholder_3RZ"));
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
改为
try { seedurl_news = eval($(".news-link"));
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
还有是这次的链接是直接给好的,不用加前缀了。
try {
href=$(e).attr("href");
console.log(href);
myURL=href
} catch (e) { console.log('识别种子页面中的新闻链接出错:' + e) }
之后的搜索也演示一下
可以正常地工作φ(゜▽゜*)♪
最近糖和橡胶的价格都比较低迷,而且从基本面来看比较疲软,没有上涨动力,市场看空情绪强烈,但下跌空间已经不大。所以交易的机会并不好找。而这个爬虫可以帮我们找到一些关于这些东西的报告,从而帮助交易者们抓住市场机遇,实现财富梦想,走上人生巅峰。也算是为社会做出了一点微小的贡献。
(其实这个爬虫只有我自己在用)
再次完结撒花★,°:.☆( ̄▽ ̄)/$:.°★ 。
中国新闻网
这个就是老师给示例用的网站啦,这个网站也是特别得耿直,什么东西都给得很明白,也是比较好爬取的网站。似乎需要做的就是把老师给的东西拼装在一起然后点击"运行"就可以了。前面两个代码也是在老师的代码的基础上改装简化得来的。
虽然这样显得我不爱学习,但这样确实方便而且高效。
老师的代码
var fs = require('fs');
var myRequest = require('request');
var myCheerio = require('cheerio');
var myIconv = require('iconv-lite');
require('date-utils');
var mysql = require('./mysql.js');
var source_name = "中国新闻网";
var domain = 'http://www.chinanews.com/';
var myEncoding = "utf-8";
var seedURL = 'http://www.chinanews.com/';
var seedURL_format = "$('a')";
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('#pubtime_baidu').text()";
var author_format = "$('#editor_baidu').text()";
var content_format = "$('.left_zw').text()";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var source_format = "$('#source_baidu').text()";
var url_reg = /\/(\d{4})\/(\d{2})-(\d{2})\/(\d{7}).shtml/;
var regExp = /((\d{4}|\d{2})(\-|\/|\.)\d{1,2}\3\d{1,2})|(\d{4}年\d{1,2}月\d{1,2}日)/
//防止网站屏蔽我们的爬虫
var headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36'
}
//request模块异步fetch url
function request(url, callback) {
var options = {
url: url,
encoding: null,
//proxy: 'http://x.x.x.x:8080',
headers: headers,
timeout: 10000 //
}
myRequest(options, callback)
};
seedget();
function seedget() {
request(seedURL, function(err, res, body) { //读取种子页面
// try {
//用iconv转换编码
var html = myIconv.decode(body, myEncoding);
//console.log(html);
//准备用cheerio解析html
var $ = myCheerio.load(html, { decodeEntities: true });
// } catch (e) { console.log('读种子页面并转码出错:' + e) };
var seedurl_news;
try {
seedurl_news = eval(seedURL_format);
} catch (e) { console.log('url列表所处的html块识别出错:' + e) };
seedurl_news.each(function(i, e) { //遍历种子页面里所有的a链接
var myURL = "";
try {
//得到具体新闻url
var href = "";
href = $(e).attr("href");
if (href == undefined) return;
if (href.toLowerCase().indexOf('http://') >= 0) myURL = href; //http://开头的
else if (href.startsWith('//')) myURL = 'http:' + href; 开头的
else myURL = seedURL.substr(0, seedURL.lastIndexOf('/') + 1) + href; //其他
} catch (e) { console.log('识别种子页面中的新闻链接出错:' + e) }
if (!url_reg.test(myURL)) return; //检验是否符合新闻url的正则表达式
//console.log(myURL);
var fetch_url_Sql = 'select url from fetches where url=?';
var fetch_url_Sql_Params = [myURL];
mysql.query(fetch_url_Sql, fetch_url_Sql_Params, function(qerr, vals, fields) {
if (vals.length > 0) {
console.log('URL duplicate!')
} else newsGet(myURL); //读取新闻页面
});
});
});
};
function newsGet(myURL) { //读取新闻页面
request(myURL, function(err, res, body) { //读取新闻页面
//try {
var html_news = myIconv.decode(body, myEncoding); //用iconv转换编码
//console.log(html_news);
//准备用cheerio解析html_news
var $ = myCheerio.load(html_news, { decodeEntities: true });
myhtml = html_news;
//} catch (e) { console.log('读新闻页面并转码出错:' + e);};
console.log("转码读取成功:" + myURL);
//动态执行format字符串,构建json对象准备写入文件或数据库
var fetch = {};
fetch.title = "";
fetch.content = "";
fetch.publish_date = (new Date()).toFormat("YYYY-MM-DD");
//fetch.html = myhtml;
fetch.url = myURL;
fetch.source_name = source_name;
fetch.source_encoding = myEncoding; //编码
fetch.crawltime = new Date();
if (keywords_format == "") fetch.keywords = source_name; // eval(keywords_format); //没有关键词就用sourcename
else fetch.keywords = eval(keywords_format);
if (title_format == "") fetch.title = ""
else fetch.title = eval(title_format); //标题
if (date_format != "") fetch.publish_date = eval(date_format); //刊登日期
console.log('date: ' + fetch.publish_date);
fetch.publish_date = regExp.exec(fetch.publish_date)[0];
fetch.publish_date = fetch.publish_date.replace('年', '-')
fetch.publish_date = fetch.publish_date.replace('月', '-')
fetch.publish_date = fetch.publish_date.replace('日', '')
fetch.publish_date = new Date(fetch.publish_date).toFormat("YYYY-MM-DD");
if (author_format == "") fetch.author = source_name; //eval(author_format); //作者
else fetch.author = eval(author_format);
if (content_format == "") fetch.content = "";
else fetch.content = eval(content_format).replace("\r\n" + fetch.author, ""); //内容,是否要去掉作者信息自行决定
if (source_format == "") fetch.source = fetch.source_name;
else fetch.source = eval(source_format).replace("\r\n", ""); //来源
if (desc_format == "") fetch.desc = fetch.title;
else fetch.desc = eval(desc_format).replace("\r\n", ""); //摘要
// var filename = source_name + "_" + (new Date()).toFormat("YYYY-MM-DD") +
// "_" + myURL.substr(myURL.lastIndexOf('/') + 1) + ".json";
// 存储json
// fs.writeFileSync(filename, JSON.stringify(fetch));
var fetchAddSql = 'INSERT INTO fetches(url,source_name,source_encoding,title,' +
'keywords,author,publish_date,crawltime,content) VALUES(?,?,?,?,?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,
fetch.title, fetch.keywords, fetch.author, fetch.publish_date,
fetch.crawltime.toFormat("YYYY-MM-DD HH24:MI:SS"), fetch.content
];
//执行sql,数据库中fetch表里的url属性是unique的,不会把重复的url内容写入数据库
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
}
}); //mysql写入
});
}
十分得工整美观严谨,考虑到了各种的情况,老师果然超级厉害的。(❁´◡`❁)
存进了数据库,然后也就是搜索的功能。这里也演示一下。
这里也可以找到关于股市的消息。
学习感受与体会
跌跌撞撞地完成了这个作业,还有很多不完善的地方。很大程度上是因为我刚开学的时候没有认真学习,天天沉迷守望先锋,导致学习进度落后,真正开始做这个作业的时候就只剩差不多两周了。然而这个本来是8周的作业。
在这次之后,我觉得我应该更注重平时的学习,把重要的东西放在每一天的日常中,这样才能有最好的学习效果,而且到截止日期的时候也不会那么忙乱。不像这一次,整个五一的假期都用来补作业了。
注重平时的学习,这是我最大的体会。
另外就是通过这次的学习,我似乎摸到了大学学习的门路,就是很注重自学。遇到的很多问题都是自己想办法解决,不会的基础知识也要自己去了解。老师在课堂上告诉的并不多,而且也不会涵盖所有的技术问题。所以有问题都要先通过各种方式尝试自己解决。
通过这次的作业,也熟练了这样的学习方式。
学习方式的改变也是这次作业的另一个收获。
在尝试做爬虫的过程中还是遇到了不少的技术问题,而且有的比较独特,在网上也找不到很好的对应的解法,不过最终都自己把它尝试出来了,也算是一个进步吧。
最后附上要用到的其他代码
mysql.js
var mysql = require("mysql");
var pool = mysql.createPool({
host: '127.0.0.1',
user: 'root',
password: 'iuXIY26E8a:t',
database: 'crawl'
});
var query = function(sql, sqlparam, callback) {
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, sqlparam, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
var query_noparam = function(sql, callback) {
pool.getConnection(function(err, conn) {
if (err) {
callback(err, null, null);
} else {
conn.query(sql, function(qerr, vals, fields) {
conn.release(); //释放连接
callback(qerr, vals, fields); //事件驱动回调
});
}
});
};
exports.query = query;
exports.query_noparam = query_noparam;
前端的
var express = require('express');
var mysql = require('./mysql.js')
var app = express();
//app.use(express.static('public'));
app.get('/7.03.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.03.html");
})
app.get('/7.04.html', function(req, res) {
res.sendFile(__dirname + "/" + "7.04.html");
})
app.get('/process_get', function(req, res) {
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8' }); //设置res编码为utf-8
//sql字符串和参数
var fetchSql = "select url,source_name,title,author,publish_date from fetches where title like '%" +
req.query.title + "%'";
mysql.query(fetchSql, function(err, result, fields) {
console.log(result);
res.end(JSON.stringify(result));
});
})
var server = app.listen(8080, function() {
console.log("访问地址为 http://127.0.0.1:8080/7.03.html")
})
网页的
<!DOCTYPE html>
<html>
<body>
<form action="http://127.0.0.1:8080/process_get" method="GET">
<br> 标题:<input type="text" name="title">
<input type="submit" value="Submit">
</form>
<script>
</script>
</body>
</html>
终于写完了★,°:.☆( ̄▽ ̄)/$:.°★ 。