在閒暇之餘,咱們常常會逛各類社區,逛掘金看技術軟文,逛虎撲看今日賽事,逛頭條看熱門時事,逛 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 代碼自行嘗試。
若是以爲有幫助到你,你懂的~