在闲暇之余,咱们常常会逛各类社区,逛掘金看技术软文,逛虎扑看今日赛事,逛头条看热门时事,逛 91……css
每一个社区都有各类各样的资讯,但有时咱们只想看某个社区的某些资讯。那咱们能不能将这些社区里咱们想要的信息作一下整合 定制成本身的“今日头条”呢?html
思路前端
天天定时抓取 资讯的标题和连接 整合后发布到本身的网站 这样天天只要打开本身的网站就能够看到属于本身的今日头条啦~node
个人今日头条jquery
ok,开撸...linux
npm init -y
today's hot │ README.md └───html │ │ index.html // 网站入口,用于部署github pages └───resource │ │ index.json // 资讯数据,爬取存放文件 └───tasks // 任务队列 │ │ index.js │ │ juejin.js │ │ top.js │ │ nba.js │ │ music.js │ │ jianshu.js └───tools // 工具类 │ index.js │ index.js // 工程入口 │ package.json
抓取资讯 我使用的是 puppeteer,它是 Google Chrome 团队官方的一个工具,提供了一些 API 来控制 chrome!(一听就很刺激。)git
npm i puppeteer --save
咱们先写一个简单的 demo 来了解一些 puppeteer 的基本 api.github
const puppeteer = require("puppeteer"); const task = async () => { // 打开chrome浏览器 const browser = await puppeteer.launch({ // 关闭无头模式,方便查看 headless: false }); // 新建页面 const page = await browser.newPage(); // 跳转到掘金 await page.goto("https://juejin.im"); // 截屏保存 await page.screenshot({ path: "./juejin.png" }); }; task();
ok~咱们趁阴明站长不在的时候,来掘金"拿点"东西~web
掘金的前端热门文章是我比较关注的模块,咱们来"拿"这个模块的资讯.ajax
const puppeteer = require("puppeteer"); const task = async () => { // 打开chrome浏览器 const browser = await puppeteer.launch({ headless: false }); // 新建页面 const page = await browser.newPage(); // 跳转到掘金 await page.goto("https://juejin.im"); // 菜单导航对应的类名 const navSelector = ".view-nav .nav-item"; // 前端菜单 const navType = "前端"; // 等待菜单加载完成... await page.waitFor(navSelector); // 菜单导航名称 const navList = await page.$$eval(navSelector, ele => ele.map(el => el.innerText) ); // [ '推荐', '后端', '前端', 'Android', 'iOS', '人工智能', '开发工具', '代码人生', '阅读' ] // 找出菜单中前端模块对应的索引 const webNavIndex = navList.findIndex(item => item === navType); // 点击前端模块并等待页面跳转完成 await Promise.all([ page.waitForNavigation(), page.click(`${navSelector}:nth-child(${webNavIndex + 1})`) ]); // 截屏保存 await page.screenshot({ path: "./juejin-web.png" }); }; task();
上图能够看到,咱们已经跳转到了前端模块.
接下来,咱们只要找出文章列表对应的类名就能够对它进行爬取.
const puppeteer = require("puppeteer"); const task = async () => { // 打开chrome浏览器 const browser = await puppeteer.launch({ headless: false }); // 新建页面 const page = await browser.newPage(); // 跳转到掘金 await page.goto("https://juejin.im"); // 菜单导航选择器 const navSelector = ".view-nav .nav-item"; // 文章列表选择器 const listSelector = ".entry-list .item a.title"; // 菜单类别 const navType = "前端"; await page.waitFor(navSelector); // 导航列表 const navList = await page.$$eval(navSelector, ele => ele.map(el => el.innerText) ); // 前端导航索引 const webNavIndex = navList.findIndex(item => item === navType); await Promise.all([ page.waitForNavigation(), page.click(`${navSelector}:nth-child(${webNavIndex + 1})`) ]); // 等待文章列表选择器加载完成 await page.waitForSelector(listSelector, { timeout: 5000 }); // 经过选择器找到对应列表项的标题和连接 const res = await page.$$eval(listSelector, ele => ele.map(el => ({ url: el.href, text: el.innerText })) ); // [ { url: 'https://juejin.im/post/5dd55512f265da47a807cc06', // text: 'if 我是前端Leader,怎么走出小微前端团队的围墙?' }, // { url: 'https://juejin.im/post/5dd49a45e51d45400206a655', // text: 'Koa仍是那个Koa,可是Nodejs已经再也不是那个Nodejs' }, // { url: 'https://juejin.im/post/5dd4b991e51d450818244c30', // text: 'WebSocket 原理浅析与实现简单聊天' },... }; task();
ok,咱们已经成功拿到了掘金前端热门文章的内容,趁站长还没来,赶忙溜~其余网站也是同样的方法,这里就不啰嗦了~
咱们拿到了资讯,接下来对它进行保存。
由于只是玩具级别的 demo,这里就不用数据库了,简单的用 json 进行保存。
// resource/index.json { "data": [] }
咱们基于 nodejs fs 文件操做模块,简单封装读写方法。
// tools/index.js const fs = require("fs"); const fileServer = { // 写文件 write(path, text) { fs.writeFileSync(path, text); }, // 读文件 read(path) { return fs.readFileSync(path); } };
接下来,咱们只要在每次获取完资讯,将内容写进文件就行了
const { fileServer } = require("./tools"); const path = require("path"); const task = () => { // 获取资讯任务 const getMsgTask = Promise.all(tasks()); getMsgTask.then(res => { // 读取json const { data } = JSON.parse( fileServer.read(path.join(resourcePath, "./index.json")).toString() ); // ... 此处省略对资讯 格式化内容 const text = msgHandle(res); // 写入资讯 fileServer.write( path.join(resourcePath, "./index.json"), JSON.stringify({ data: [ { date: now, text }, ...data ] }) ); }); };
保存完资讯,咱们只要请求这个文件,将它渲染出来就行了~
// html/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" /> <title>今日资讯</title> <script src="https://cdn.bootcss.com/marked/0.7.0/marked.min.js"></script> <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script> </head> <body> <div id="content"></div> </body> <script> (function() { $(document).ready(function() { $.ajax({ url: "http://localhost:8888/index.json", dataType: "json", success(data) { const content = data.data.reduce((a, b) => a + b.text, ""); // 资讯我使用的是markdown进行保存,因此用marked进行转换 $("#content").html(marked(content)); } }); }); })(); </script> </html>
定时任务使用的是node-schedule,很是简单易用的一个 nodejs 库。
// 每日18时定时任务 function crontab() { schedule.scheduleJob(`00 00 18 * * *`, mainTask); } // 任务 function mainTask(){...}
部署我采用的是 docker + github pages 。
docker 部署这里有两个要注意的地方
# Dockerfile FROM node:10-slim # 建立项目代码的目录 RUN mkdir -p /workspace # 指定RUN、CMD与ENTRYPOINT命令的工做目录 WORKDIR /workspace # 复制宿主机当前路径下全部文件到docker的工做目录 COPY . /workspace # 清除npm缓存文件 RUN npm cache clean --force && npm cache verify # 若是设置为true,则当运行package scripts时禁止UID/GID互相切换 # RUN npm config set unsafe-perm true RUN npm config set registry "https://registry.npm.taobao.org" RUN npm install -g pm2@latest # Install latest chrome dev package and fonts to support major charsets (Chinese, Japanese, Arabic, Hebrew, Thai and a few others) # Note: this installs the necessary libs to make the bundled version of Chromium that Puppeteer # installs, work. 此处有墙... # https://github.com/GoogleChrome/puppeteer/blob/master/docs/troubleshooting.md RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \ && apt-get update \ && apt-get install -y google-chrome-unstable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf \ --no-install-recommends \ && rm -rf /var/lib/apt/lists/* # 只安装package.json dependencies RUN npm install --production RUN npm i puppeteer # 设置时区 RUN rm -rf /etc/localtime && ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime EXPOSE 8888 CMD [ "pm2-docker", "start", "pm2.json" ]
构建镜像 shell
# build.sh docker build -t today-hot .
启动容器 shell
# run.sh curPath=`cd $(dirname $0);pwd -P` docker run --name todayHot -d -v $curPath:/workspace -p 8888:8888 today-hot
接下来只要把 html 文件部署到网站上便可,咱们这里使用 github-pages ,免费的静态网站托管平台~
npm install gh-pages --save
在 package.json 定义 scripts
"scripts": { "deploy": "gh-pages -d html" } npm run deploy 将前端资源推送到github上,而后经过 xxx.github.io/xxx 就能够访问了
本文主要讲解的是思路,具体代码以下,爬虫 服务并无部署到服务器,你们能够 download 代码自行尝试。
若是以为有帮助到你,你懂的~