爬虫经历分享
老师的要求如下
1.找网页
新闻链接:https://sports.sina.com.cn
新浪体育
新浪体育
新浪体育
永远的神!!!!!
为什么选某浪网页——当然是多次尝试的结果,使着爬了很多个网页,但好多爬下来不是有问题就是中文乱码,气死,只有这个某浪网页,非常nice!!!!
2.读取种子页面
应该也可以解释为读取它的源代码吧,我按照老师说的点击右键,选择查看网页源代码,但是Mac操作不是这样!
你要——以下来自百度百科
1.首先打开safari浏览器,点击上方的菜单栏,选择“偏好设置”选项。
2.然后在弹出来的窗口中,选择 “高级”页签,勾选 “在菜单栏里显示开发菜单”。
3.此时回到要查看源码的页面,点击上方的开发选项菜单,点击显示页面源文件,显示页面资源,显示错误控制台三项即可。
4.此时回到网页中,即可看到下方显示的当前网页源码了。
然后我也不咋看得懂(到后期就差不多可以看懂了),然后进行下一步——the next one!
3.分析出种子页面里所有的新闻链接
(说实话,刚开始的时候,这句话我就不太懂老师要我们干什么了)
分析出种子页面的新闻链接,也就是爬取出所有的a链接
一开始我也看不太懂老师的代码,但在网上看了一些爬虫的视频和代码,又跑了跑老师给的代码也就差不多有了初步的了解。
然后就尝试开始自己写代码,跟老师不同的是,我是引入了http库的request函数来写,这表示我的作业有着自己的思想哈哈哈,但其实本质上也差不多…
先引入包
const https = require ('https')
const cheerio = require ('cheerio')
再向网页发送请求
//引入http模块
var myurl = 'https://sports.sina.com.cn'
//创建请求对象
var req = https.request(myurl,res => {
let chunks =[]//因为chunk很多,所以用chunks接受一下
//data事件就是有数据传过来了,有数据响应了,然后用chunk接收一下
res.on('data',chunk => chunks.push(chunk))
//end的时候说明所有数据传输完毕,这个时候可以来一个回调
res.on('end',()=>{
//拼接成一个完整的数据流,再转换为html代码
let htmlstr = Buffer.concat(chunks).toString('utf-8')
//console.log(htmlstr)
var all=[]
let $ = cheerio.load(htmlstr)//用cheerio解析整个网页,$对象就等于我们的解块对象
$('a').each((index,item) =>{
//each函数进行遍历
//console.log( $(item).attr('href'))
all.push( $(item).attr('href'))
})
console.log(all)
})
})
//请求发送出去
req.end()
运行结果如下:
感觉自己是完成了第一步,这样运行后的结果就是打印出了新浪体育网所有a链接,但是但是后面遇到的问题真的是各种各样!!!!!!!
根据现在我们打印出的结果可以看出,有一些网页是不规范的,所以我们需要写一段代码,使其输出格式规范
代码如下:
if (typeof(mine) == "undefined") return true;
if (mine.toLowerCase().indexOf('http://') >= 0 || mine.toLowerCase().indexOf('https://') >= 0) mine = mine; //http://开头的或者https://开头
else if (mine.startsWith('//')) mine = 'http:' + mine ; 开头的
else mine = myurl.substr(0, myurl.lastIndexOf('/') ) + mine; //其他
4.开始正式爬取内容(完整代码见1.js)
然后接下来根据自己要爬取的内容来写变量,老师有给例子说明
也是先引入所需模块
//引入http模块
const https = require ('https')
const http = require ('http')
const cheerio = require ('cheerio')
const fs = require ('fs')
require('date-utils');
var myurl = 'https://sports.sina.com.cn'//创建请求对象
var keywords_format = " $('meta[name=\"keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('meta[property=\"article:published_time\"]').eq(0).attr(\"content\")";
var author_format = "$('meta[property=\"article:author\"]').eq(0).attr(\"content\")";
var desc_format = " $('meta[name=\"description\"]').eq(0).attr(\"content\")";
var url_reg =/\/.+\/\d{4}-\d{2}-\d{2}\/.+[.]shtml/;
var source_name ='新浪新闻网'
var myEncoding = "utf-8";
防止网站屏蔽我们的爬虫
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'
}
一开始我each每一个‘a’链接,然后就直接开始进行有关数据库的操作,但我发现这样,每次爬取的信息都是一样的!!都是新浪体育总页面,归结原因为:一直拿到的是首页内容是因为你只访问了首页的url,没有访问其他链接。所以现在需要引入一个newget函数来继续访问其他链接再进行文件操作。
但是问题又来了直接对访问到的链接进行newget函数,直接报错
心态崩掉,呜呜呜~
后来经过高人指点哈哈哈哈发现:因为我们所爬取到的‘a’链接有些是’http‘开头,有些是‘https开头,所以就需要分类写来,听起来很复杂,但是写好一个newget函数后,另一个复制后改一改就好啦
分类如下:
newget函数如下:
function newget(mine)
{
var req = http.request(mine,res =>
{
var chunks =[]//因为chunk很多,所以用chunks接受一下
//data事件就是有数据传过来了,有数据响应了,然后用chunk接收一下
res.on('data',chunk => chunks.push(chunk))
//end的时候说明所有数据传输完毕,这个时候可以来一个回调
res.on('end',()=>{
//拼接成一个完整的数据流,再转换为html代码
var htmlstr = Buffer.concat(chunks).toString('utf-8')
//console.log(htmlstr)
var $ = cheerio.load(htmlstr)//用cheerio解析整个网页,$对象就等于我们的解块对象
var fetch = {};
fetch.title = "";
fetch.content="";
fetch.keywords = "";
fetch.date = "";
fetch.author = "";
fetch.Myurl=myurl;
fetch.url=mine;
fetch.source_name = source_name;
fetch.source_encoding = myEncoding;
fetch.crawltime=new Date();
console.log('转码成功:'+fetch.url)
if (keywords_format == "") fetch.keywords =""; // 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); //刊登日期
if (author_format == "") fetch.author = source_name; //eval(author_format); //作者
else fetch.author = eval(author_format);
if (desc_format == "") fetch.content = "";
else fetch.content = eval(desc_format);
var filename = source_name + "_" + (new Date()).valueOf()+"_" + ".json";
fs.writeFileSync(filename, JSON.stringify(fetch));
})
})
req.end()//请求发出去
}
newget2函数就只需要把newget函数开始的http改为https就好啦
然后开始运行!!!!
成功!!(其实在这个爬虫过程经历居多失败,各种报错,这里就不一一展示了)
运行结果如下:
给爷激动死了!!!!
5.创建并使用数据库
在终端输入/usr/local/MySQL/bin/mysql -u root -p进入mysql
然后输入密码
再输入
create database crawl;
use crawl;(一定注意句末+分号,不然就会错很多次)
6.存入数据库(完整代码见2.js)
先根据自己需求创建表(fetches代码)创建成功后,再稍微修改了下数据库的代码
但是运行代码之后
果不其然
报错了😭😭😭😭😭
有问题,解决!!!
然后
报错原因说明我的插入语句有问题
我写了8个‘?’
但是我要插入的是7个字段
于是改!!!!
新鲜代码出炉
//var filename = source_name + "_" + (new Date()).valueOf()+"_" + ".json";
//fs.writeFileSync(filename, JSON.stringify(fetch));
var fetchAddSql = 'INSERT INTO fetches(url,source_name,source_encoding,title,' +'keywords,author,crawltime,content) VALUES(?,?,?,?,?,?,?,?)';
var fetchAddSql_Params = [fetch.url, fetch.source_name, fetch.source_encoding,fetch.title, fetch.keywords, fetch.author,fetch.crawltime, fetch.content];
mysql.query(fetchAddSql, fetchAddSql_Params, function(qerr, vals, fields) {
if (qerr) {
console.log(qerr);
}
});
成功
查看下fetches里面的author, title;
select author, title,url from fetches;
太激动了!!!!!
看看新闻网
和上述过程相同,爬取其他网友内容也差不多如此(完整代码见7.j s)
下面来爬取——看看新闻网——‘http://www.kankanews.com/’
因为开头为http开头
所有对于2.js的代码修改如下:
1.对网页的请求改为http库的request函数
2.修改变量
var keywords_format = " $('meta[name=\"Keywords\"]').eq(0).attr(\"content\")";
var title_format = "$('title').text()";
var date_format = "$('.time').text()";
var author_format = "$('.resource').text()";
var desc_format = " $('meta[name=\"Description\"]').eq(0).attr(\"content\")";
var source_name ='看看新闻网'
var url_reg = /a\/(\d{4})-(\d{2})-(\d{2})\/(\d{10}).shtml/;
var myEncoding = "utf-8";
运行结果:
也就成功了
搜狐
同理也是修改变量
完整见6.js
var source_name = "搜狐";//来源
var myencoding = "utf-8";//解码
var seedurl = 'http://news.sohu.com/';//主页面
var seedurl_format = "$('a')";//寻找子页面
var keywords_format = "$('meta[name=\"keywords\"]').eq(0).attr(\"content\")";//关键词
var title_format = "$('title').text()";//标题
var date_format = "$('meta[property=\"og:release_date\"]').eq(0).attr(\"content\")";//日期
var author_format = "$('meta[name=\"mediaid\"]').eq(0).attr(\"content\")";//编辑
var content_format = "$('article').text()";//文章
var url_reg = /\/a\/(\d{9})\_/;//用于匹配主页面中的新闻子页面的正则表达式
结果
7.用mysql查看自己爬取到的数据
然后接下来操作主要是针对新浪体育
试了一下查询NBA
结果乐观
8.用网页发送请求到后段查询
先创建一个前端(文件4.html)
注意:要用英文!否则会乱码:(其实也可以在开头加
<meta charset="utf-8" />
,将代码都转换成——utf-8)
就会像这样乱码
然后再创建一个后端(文件4)
运行后查询结果如下:
9.用express构建网站访问mysql
同样先创建前端(注意用英文)(见5.html)
再创建后端文件5.js
`
结果如下:
10.用express脚手架来创建网络框架
好家伙按老师那样来,一直报错,说:“command not find express"
然后求助大佬,反正就是express路径不对
于是解决后的方法就是:
先手动创建一个文件叫“e_1”
然后新建位于文件夹的终端窗口
在终端输入 express -e
就可以了
再输入npm install mysql --save将mysql包安装到项目中,并将依赖项保存进package.json
再输入npm install将package.json中列举的依赖项全部安装,完成网站搭建
之后,在routes目录下修改index.js
然后,在e_1/public/下创建一个search.html
但是
报错报错还是报错!我根本在页面就查询不了
解决不了
找助教——发现问题——解决问题
首先是index.js里面,写的sql语句有个小问题是authorfrom,这两个连在一起了导致无法正常查询数据库
修改!主要问题还是在index.js里的process_get
方法:在search.html里面对执行回车操作在form里加了个action,让它跳转到index.js的/process_get来处理
代码新鲜出炉
index修改
var express = require('express');
var router = express.Router();
var mysql = require('../mysql.js');
/* GET home page. */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/process_get', function(request, response) {
//sql字符串和参数
var fetchSql = "select url,source_name,title,author " +
"from fetches where title like '%" + request.query.title + "%'";
console.log(request.query.title);
mysql.query(fetchSql, function(err, result, fields) {
response.writeHead(200, {
"Content-Type": "application/json"
});
if(err){
console.log(err);
return;
}
console.log(result);
response.write(JSON.stringify(result));
response.end();
});
});
module.exports = router;
search修改
<!DOCTYPE html>
<html>
<header>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</header>
<body>
<form action="/process_get">
<br> 标题:<input type="text" name="title">
<input class="form-submit" type="button" value="查询">
</form>
<div class="cardLayout" style="margin: 10px 0px">
<table width="100%" id="record2"></table>
<tr class="cardLayout"><td>url</td><td>source_name</td><td>title</td><td>author</td><td>publish_date</td></tr>
</div>
<script>
$(document).ready(function() {
$("input:button").click(function() {
$.get('/process_get?title=' + $("input:text").val(), function(data) {
$("#record2").empty();
$("#record2").append('<tr class="cardLayout"><td>url</td><td>source_name</td>' +
'<td>title</td><td>author</td></tr>');
for (let list of data) {
let table = '<tr class="cardLayout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#record2").append(table + '</td></tr>');
}
});
});
});
</script>
</body>
</html>
然后在终端输入:node ./bin/www
这个跟老师输入得不太一样
得看自己目录下的是什么
然后用chrome浏览器打开http://127.0.0.1:3000/search.html
一定一定要记得加端口名
否则就会像我一样一直打不开
结果如下:
输入NBA:
这就完了吗?
不没有!
好家伙,我今天又跑了下之前的代码,然后我发现5.j s跑不了了,太伤心了,一直报错说Error: Cannot find module ‘express’,一直无法解决,然后经过指点后发现:
我的5.js所在的目录下没有node_modules这个文件夹,也就是没有存放其他的模块,所以一直报错,解决方法:在其他地方找到node_modules这个文件夹后,copy到5.js所在文件,再运行就好了!
11.进一步使我们网页更美观
1.再次对index.js和search.html进行修改
修改如下:
index.js
var express = require('express');
var router = express.Router();
var mysql = require('../mysql.js');
/* GET 主页 */
router.get('/', function(req, res, next) {
res.render('index', { title: 'Express' });
});
router.get('/process_get', function(request, response) {
//sql
var fetchSql = "select url,source_name,title,author " +
"from fetches where author like '%" + request.query.author + "%'";
var tarray=request.query.title.split(" ");
for(var i=0;i<tarray.length;i++){
fetchSql+="and title like '%"+ tarray[i] + "%'";
}
mysql.query(fetchSql, function(err, result, fields) {
response.writeHead(200, {
"Content-Type": "application/json"
});
response.write(JSON.stringify(result));
response.end(); ``
});
});
module.exports = router;
修改一,增加了搜索参数author
修改二,由于title大多数较长,可改成多关键词查询
search.html修改
<!DOCTYPE html>
<html>
<header>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
</header>
<head>
<style>
.cardLayout{
border:rgb(224, 23, 117) solid 10px;
margin: 5px 0px;
}
tr{
border: solid 3px;
}
td{
border:solid 3px;
}
</style>
</head>
<body background="background.jpeg"
style="background-repeat:no-repeat;
background-size:100% 100%;
background-attachment: fixed;">
<form>
<br> 标题:<input type="text" id="input1" name="title_text">
<br> 作者:<input type="text" id="input2" name="title_text">
<input class="form-submit" type="button" id="btn1"value="查询">
<input type="reset">
</form>
<div class="cardLayout">
<table width="100%" id="record2"></table>
</div>
<script>
$(document).ready(function() {
$("#btn1").click(function() {
$.get('/process_get?title=' + $("#input1").val()+'&author='+$("#input2").val(), function(data) {
$("#record2").empty();
$("#record2").append('<tr class="cardLayout"><td>url</td><td>source_name</td>' +
'<td>title</td><td>author</td><td>publish_date</td></tr>');
for (let list of data) {
let table = '<tr class="cardLayout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#record2").append(table + '</td></tr>');
}
});
});
});
</script>
</body>
</html>
修改主要是增加了一段style,使页面更好看(最近好喜欢玫红色哈哈哈哈)
然后对body加了一段代码
<body background="background.jpeg"
style="background-repeat:no-repeat;
background-size:100% 100%;
background-attachment: fixed;">
也就是这样:
多关键词搜索
然后重点来了!!!!!
进行分页操作!!!!——因为一页实在是太多了(完整代码见search.html)
这一步都是跟着别人学的(参考:https://blog.csdn.net/qq_38071755/article/details/81944359)
1.第一步:对html文件进行微调:先在原先基础上引入bootstrap包
主要就是要引入bootstrap包!!!!!
<head>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.js"></script>
<link href="http://www.itxst.com/package/bootstrap-table-1.14.1/bootstrap-4.3.1/css/bootstrap.css" rel="stylesheet" />
<link href="http://www.itxst.com/package/bootstrap-table-1.14.1/bootstrap-table-1.14.1/bootstrap-table.css" rel="stylesheet" />
<script src="http://www.itxst.com/package/bootstrap-table-1.14.1/bootstrap-table-1.14.1/bootstrap-table.js"></script>
<style>
.cardLayout{
border:rgb(224, 23, 117) solid 10px;
margin: 5px 0px;
}
tr{
border: solid 2px;
}
td{
border:solid 2px;
}
</style>
</head>
2.第二步:紧接着修改get函数中展示表单的部分(使用bootstrap方法)
<script>
$(document).ready(function() {
$("#btn1").click(function() {
/*$.get('/process_get?title=' + $("#input1").val()+'&author='+$("#input2").val(), function(data) {
$("#record2").empty();
$("#record2").append('<tr class="cardLayout"><td>url</td><td>source_name</td>' +
'<td>title</td><td>author</td><td>publish_date</td></tr>');
for (let list of data) {
let table = '<tr class="cardLayout"><td>';
Object.values(list).forEach(element => {
table += (element + '</td><td>');
});
$("#record2").append(table + '</td></tr>');
}
});*/
$.get('/process_get?title=' + $("#input1").val()+'&author='+$("#input2").val(), function(data) {
$("#record2").bootstrapTable({
search:true, //加上搜索控件
method: 'get', //请求方式
pagination: true, //是否显示分页
striped: true, //是否显示行间隔色
uniqueId: "userId", //每一行的唯一标识,一般为主键列
pageSize: 5, //每页的记录行数
sidePagination : 'client',
columns:[{
field:'url', //对应数据库字段名
title:'链接',
},{
field:'source_name',
title:'来源'
},{
field:'title',
title:'标题'
},{
field:'author',
title:'作者'
}],
data: data,
});
});
});
});
</script>
成果展示:
搜索🔍某一个关键词后,可以分页展示,并且显示相关话题的总条数
学习分页成功
啊啊啊啊啊救命,老师突然发了要求,我发现我没有对热度进行分析,于是我准备加一个词云,但是我就不会用python写,这远远超过了我的能力范围,就只好运用一些现有的工具,在这里我就用了Word Art这个工具来搞
词云搞好之后,就要想办法在前端显示
于是我在search中又加了一段代码
<br/>
<a href="/e_4/public/Word Cloud.png"><input type = "button" value="Word Cloud"></a>
<br />
于是前端就有了Word Cloud 的按钮🔘
点击后就可以看见我的词云图片了
啊啊啊啊啊,终于结束了,感觉自己被掏空
完结——撒花🎉🎉🎉
12.总结
真的太不容易了,一开始对js,html的各种语法都不懂,上网各种乱看,摸不着头脑,后来发现还是自己动手实操最有用,跟着老师的代码一步一步跑,有什么问题通过问老师,问助教,问大佬解决,期间也遇到了很多困难,各种报错,在一开始选取爬取网站就经历了很多挫折,找到全是“utf-8”的真不容易,在最后一步分页操作我觉得也很难,期间试了很多种写法一直不可以,最后是在网上看见了一篇教程,一步步跟着写,最后就可以了。以上就是第一次的爬虫经历,希望之后可以越来越好!