Welcome to nodecrawler 👋
node 爬虫笔记
🏠 Homepage
一、什么是爬虫?
爬虫简而言之就是爬去网页上的信息。而网页结构就是一个树形结构,就像一个蜘蛛网一样。而爬虫程序就像一个蜘蛛,在这个蜘蛛网上去收取我们感兴趣的信息。
二、开始写爬虫前需要确定的两个东西。
- Where to crawler? (要爬那的信息?)。
- What to crawler? (你要爬什么信息?)。
工艺利其事必先利器
刚开始找了几个 node 爬虫库,但是效果不是很理想。不过皇天不负有心人,不过最终还是让我找到了一个:Apify.
三、使用 Apify 来开始我的爬虫之旅
1. 首先新建一个工程然后安装 apify 依赖。
npm i apify -S
接下来要确定一下爬取那个网站的信息(以豆瓣电影 Top 250 为例)
2.现在我们已经确定了要爬取的 url(movie.douban.com/top250),现在开始编写代码。
// 引入apify
const Apify = require('apify');
3.apify 提供了一个动态队列(requestQueue)来管理我们要爬取的 url,我们可以使用它来管理我们所有要爬取的 url。
const Apify = require('apify');
Apify.main(async ()=>{
// 首先创建一个请求队列
const requestQueue = await Apify.openRequestQueue();
// 将要爬取的url添加到队列当中
await requestQueue.addRequest('https://movie.douban.com/top250');
})
5.已经有了请求队列,接下来要做的是What to crawler。需要一个方法去解析请求的网页内容。
定义一个函数来解析网页内容,该函数之后会传入apify爬虫的一个实例当中
async function handlePageFunction({ request, $ }) {
// 是不是对$很熟悉,其实就是node里的jquery
// 先简单打印下网页的title.
const title = $('title').text();
console.log(`网页的title:${title}`);
}
6.最后,创建一个CheerioCrawler 爬虫实例,并将requestQueue,handlePageFunction作为参数传入。然后启动爬虫
const crawler = new Apify.CheerioCrawler({
requestQueue,
handlePageFunction
})
// 启动爬虫
await crawler.run();
我们把代码做一下整合,然后启动爬虫。
const Apify = require('apify');
Apify.main(async () => {
// 创建请求队列
const requestQueue = await Apify.openRequestQueue();
// 将要爬取的url添加到队列当中
await requestQueue.addRequest({ url: 'https://movie.douban.com/top250' });
const handlePageFunction = async ({ request, $ }) => {
// 是不是对$很熟悉,其实就是node里的jquery
// 先简单打印下网页的title.
const title = $('title').text();
console.log(`网页的title:${title}`);
}
//创建一个CheerioCrawler,将requestQueue,handlePageFunction作为参数传入
const crawler = new Apify.CheerioCrawler({
requestQueue,
handlePageFunction
})
// 启动爬虫
await crawler.run();
})
运行代码,页面的标题成功被爬取
到这里,就已经实现了一个简易的爬虫,但是还没有实现我们的需求(爬取完整的top250)。我们需要动态的去添加url,才能爬取到完整的250部电影。
8.获取所有要爬取的页面
初始url是首页,我们需要获取所有页码的页面,通过解析页面,我们可以通过以apify提供的一个动态添加url到队列的方法来将我们想要爬去的页面添加到请求队列当中。
const {
utils: { enqueueLinks },
} = Apify;
await enqueueLinks({
$,
requestQueue,
selector: '.next > a', // 跳转到下一页的a标签
baseUrl: request.loadedUrl, //根据baseUrl会将a中的href补全
});
9. 接下来需要修改一下handlePageFunction,来解析需要的电影信息。
/**
* 解析网页,获取电影信息
*/
function parseMovie($) {
const movieDoms = $('.grid_view .item');
const movies = [];
movieDoms.each((index, item) => {
const movie = {
rank: $(item).find('.pic em').text(), // 排名
name: $(item).find('.title').text(), // 电影名
score: $(item).find('.rating_num').text(), // 评分
sketch: $(item).find('.inq').text() // 主题
}
movies.push(movie)
})
return movies
}
10. 再把代码整合到一起,运行看看结果
const Apify = require('apify');
const {
utils: { enqueueLinks },
} = Apify;
Apify.main(async () => {
// 首先创建一个请求队列
const requestQueue = await Apify.openRequestQueue();
await requestQueue.addRequest({ url: 'https://movie.douban.com/top250' });
const crawler = new Apify.CheerioCrawler({
requestQueue,
handlePageFunction
})
async function handlePageFunction({ request, $ }) {
await enqueueLinks({
$,
requestQueue,
selector: '.next > a', // 跳转到下一页的a标签
baseUrl: request.loadedUrl, //根据baseUrl会将a中的href补全
});
const movies = parseMovie($);
movies.forEach((item, i) => {
console.log(`${item.rank}|${item.name}|${item.score}|${item.sketch}`)
})
}
// 启动爬虫
await crawler.run();
})
/**
* 解析网页,获取电影信息
*/
function parseMovie($) {
const movieDoms = $('.grid_view .item');
const movies = [];
movieDoms.each((index, item) => {
const movie = {
rank: $(item).find('.pic em').text(), // 排名
name: $(item).find('.title').text(), // 电影名
score: $(item).find('.rating_num').text(), // 评分
sketch: $(item).find('.inq').text() // 主题
}
movies.push(movie)
})
return movies
}
⭐️运行下看看结果
现在的运行结果已经满足我们的需求了,但是会不会觉得上面的代码有些麻烦,得找到链接、再转换、再添加到请求队列。可不可以给出一个url规则,然后程序自动帮我添加到队列中呢?
五、本地数据持久化
这里我们使用sqlite来做本地数据持久化,因为它是一个轻量级的数据库、而且是不需要服务端的。
- 安装sqlite3依赖
npm i sqlite3 -S
- 初始化数据库,创建movies表
const sqlite3 = require('sqlite3').verbose();
function initDB(){
let db = new sqlite3.Database('./db/crawler.db', (err) => {
if (err) {
return console.error(err.message,'啊哈');
}
console.log('Connected to the crawler database.');
});
createTable(db);
return db;
}
function createTable(db){
const sql = `CREATE TABLE IF NOT EXISTS movies(
rank TEXT,
name TEXT,
TEXT TEXT,
sketch TEXT
);`
db.run(sql,[],(err)=>{
if(err){
return console.log(err)
}
console.log('表创建成功')
})
}
3.插入数据
function insertData(db, movie = {}) {
db.serialize(function () {
db.run(`INSERT INTO movies(rank,name,score,sketch) VALUES(?,?,?,?)`, [movie.rank, movie.name, movie.score, movie.sketch], function (err) {
if (err) {
return console.log(err.message);
}
// get the last insert id
console.log(`A row has been inserted with rowid ${this.lastID}`);
});
})
}
结尾
到这里,一个简单的爬虫程序算是写完了,但是这里还少了ip代理以及请求源伪装。后面再加上