仅供学习,请勿用于非法用途
安装linux版chromedriver和linux版chrome
#!/usr/bin/env sh
# 确保脚本抛出遇到的错误
set -e
echo "安装chromedriver"
wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.6045.105/linux64/chromedriver-linux64.zip
unzip chromedriver-linux64.zip
cd chromedriver-linux64
chmod 777 chromedriver
cd ../
echo "安装chrome"
wget https://edgedl.me.gvt1.com/edgedl/chrome/chrome-for-testing/119.0.6045.105/linux64/chrome-linux64.zip
unzip chrome-linux64.zip
功能实现
- 使用selenium抓取掘金登录的二维码,使用稀土掘金app扫二维码登录
- html页面提供两个按钮,一个登录,一个查看登录状态
使用nest实现
创建项目
nest new juejin-tool
安装依赖包
pnpm i @nestjs/config @nestjs/schedule cron@3.1.3 @types/selenium-webdriver selenium-webdriver
@nestjs/config:从.env中读取配置
@nestjs/schedule:提供nest定时任务模块
cron:nest定时任务模块依赖包
selenium-webdriver:操作selenium
@types/selenium-webdriver:提供selenium使用的类型
开发模式启动服务
npm run start:dev
访问localhost:3000,以下页面为启动成功
前端提供两个按钮调用后端接口
后端提供两个接口,一个接口登录,一个接口获取当前登录状态
前端实现:
项目根路径下创建public文件夹,将文件夹作为静态资源目录
修改main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 支持静态资源访问并指定地址前缀为static(需要给create传入NestExpressApplication泛型参数才有useStaticAssets方法)
app.useStaticAssets('public', { prefix: '/pages' });
await app.listen(3000);
}
bootstrap();
在public文件夹下创建index.html文件,代码如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>掘金工具(自动签到)</title>
<script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.min.js"></script>
</head>
<body>
<button id="login">登录</button>
<button id="get-login-status">查询登录状态</button>
<div id="content">
</div>
<script>
// 登录
const loginButton = document.getElementById("login")
const content = document.getElementById("content")
loginButton.addEventListener("click", () => {
content.innerHTML = '请求发送中...'
axios({
url: "/juejin/login"
}).then(res => {
console.log(res.data)
if (res.data === '已登录') {
content.innerHTML = "已登录"
return
}
content.innerHTML = `请在20s内使用稀土掘金app扫以下二维码<br/><img src="${res.data}" />`
})
})
// 获取登录状态
const loginStatusButton = document.getElementById("get-login-status")
loginStatusButton.addEventListener("click", () => {
content.innerHTML = '请求发送中...'
axios({
url: "/juejin/login/status"
}).then(res => {
content.innerHTML = `当前登录状态为:${res.data}`
})
})
</script>
</body>
</html>
将localhost:3000指向这个页面
修改app.controller.ts文件
import { Controller, Get, Res } from '@nestjs/common';
import { AppService } from './app.service';
import { Response } from 'express';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(@Res() res: Response) {
// return this.appService.getHello();
res.redirect('/pages/index.html');
}
}
后端实现
根目录下创建.env文件添加三行配置
# chromedriver文件路径
CHROME_DRIVER_PATH=
# chrome二进制文件路径
CHROME_BINARY_PATH=
# chrome用户数据存储路径(为了保存掘金用户登录session)
CHROME_USER_DATA_DIR=
使用nest-cli生成junjin接口目录和selenium服务目录
nest g service selenium
nest g resource juejin
修改app.module.ts引入配置模块和定时任务模块
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SeleniumService } from './selenium/selenium.service';
import { JuejinModule } from './juejin/juejin.module';
import { ScheduleModule } from '@nestjs/schedule';
import { ConfigModule } from '@nestjs/config';
@Module({
imports: [JuejinModule, ScheduleModule.forRoot(), ConfigModule.forRoot()],
controllers: [AppController],
providers: [AppService, SeleniumService],
})
export class AppModule {}
修改selenium.service.ts文件
import { Injectable } from '@nestjs/common';
import { Builder, ThenableWebDriver } from 'selenium-webdriver';
import { Options, ServiceBuilder } from 'selenium-webdriver/chrome';
@Injectable()
export class SeleniumService {
webdriver(): ThenableWebDriver {
// 创建一个webdriver示例
const webdriver = new Builder();
// 创建一个remote.DriverService实例在一个子进程中管理ChromeDriver
const serviceBuilder = new ServiceBuilder(process.env.CHROME_DRIVER_PATH);
const chromeOptions = new Options();
chromeOptions
.addArguments(
'--no-sandbox',
`--user-data-dir=${process.env.CHROME_USER_DATA_DIR}`, // 设置用户数据存储路径
'user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36', // 模拟ua
)
.headless() // 无头模式启动
.setChromeBinaryPath(process.env.CHROME_BINARY_PATH);
webdriver
.forBrowser('chrome')
.setChromeOptions(chromeOptions)
.setChromeService(serviceBuilder);
return webdriver.build();
}
}
修改juejin.module.ts文件
import { Module } from '@nestjs/common';
import { JuejinService } from './juejin.service';
import { JuejinController } from './juejin.controller';
import { SeleniumService } from 'src/selenium/selenium.service';
@Module({
controllers: [JuejinController],
// 在juejin module中注册selenium service
providers: [JuejinService, SeleniumService],
})
export class JuejinModule {}
修改jujin.controller.ts文件
import { Controller, Get } from '@nestjs/common';
import { JuejinService } from './juejin.service';
@Controller('juejin')
export class JuejinController {
constructor(private readonly juejinService: JuejinService) {}
// 掘金登录
@Get('login')
login() {
return this.juejinService.login();
}
// 获取当前用户登录状态
@Get('login/status')
loginStatus() {
return this.juejinService.getStatus();
}
}
修改juejin.service.ts文件
import { Inject, Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { By } from 'selenium-webdriver';
import { SeleniumService } from 'src/selenium/selenium.service';
@Injectable()
export class JuejinService {
@Inject(SeleniumService)
seleniumService: SeleniumService;
// 登录功能
async login() {
const webdriver = this.seleniumService.webdriver();
await webdriver.get('https://juejin.cn/');
try {
const loginbutton = webdriver.findElement(By.className('login-button'));
await loginbutton.click();
await webdriver.sleep(5000);
const qrimg = webdriver.findElement(By.className('qrcode-img'));
const qrimgData = await qrimg.getAttribute('src');
setTimeout(async () => {
await webdriver.quit();
}, 20 * 1000);
return qrimgData;
} catch (e) {
await webdriver.quit();
return '已登录';
}
}
// 查看当前登录状态
async getStatus() {
console.log('获取当前登录状态');
const webdriver = this.seleniumService.webdriver();
await webdriver.get('https://juejin.cn/user/center/signin?avatar_menu');
try {
const username = await webdriver.findElement(
By.className('login-user-name'),
);
const usernameText = await username.getText();
console.log(usernameText);
await webdriver.quit();
return '未登录';
} catch (e) {
await webdriver.sleep(3000);
const username = await webdriver.findElement(By.className('username'));
const usernameText = await username.getText();
await webdriver.quit();
return `已登录,用户名为:${usernameText}`;
}
}
// 定时签到抽抽奖(每天上午10点)
@Cron(CronExpression.EVERY_DAY_AT_10AM)
async signIn() {
console.log('定时签到抽奖');
const webdriver = this.seleniumService.webdriver();
await webdriver.get('https://juejin.cn/user/center/signin?avatar_menu');
await webdriver.sleep(5 * 1000);
console.log('点击签到按钮');
let button = await webdriver.findElement(By.css('.code-calender .signin'));
await button.click();
await webdriver.sleep(5 * 1000);
console.log('点击去抽奖');
button = await webdriver.findElement(By.css('.btn-area .btn'));
await button.click();
await webdriver.sleep(5 * 1000);
console.log('点击免费抽奖');
button = await webdriver.findElement(By.css('.cost-box #turntable-item-0'));
await button.click();
await webdriver.sleep(5 * 1000);
console.log('点击收下奖励');
button = await webdriver.findElement(
By.css('.byte-modal__body .wrapper .submit'),
);
await button.click();
await webdriver.sleep(5 * 1000);
await webdriver.quit();
}
}
完整代码:https://gitee.com/xianyu10086/juejin-tool
linux使用
linux部署
# 克隆代码
git clone https://gitee.com/xianyu10086/juejin-tool
# 切换目录
cd juejin-tool
# 安装依赖包
pnpm i
# 安装chrome和chromedriver
bash install.sh
# 配置.env
vim .env
# 打包
npm run build
# pm2 守护进程启动
pm2 start npm --name juejin-tool -- run start:prod
# pm2 查看日志
pm2 logs
遇到的问题
chrome和chromedriver报错,一般是因为缺少库,使用chromedriver -v 和 chrome -v查看缺少哪些库文件,然后百度安装,我遇到的缺少问题如下
问题:error while loading shared libraries: libnss3.so
yum install nss
问题: error while loading shared libraries: libatk-bridge-2.0.so.0
yum install at-spi2-atk
问题:error while loading shared libraries: libgbm.so.1
yum install libgbm*
问题:error while loading shared libraries: libasound.so.2
yum install alsa-lib-devel
问题:error while loading shared libraries: libxkbcommon.so.0
yum install libxkbcommon
访问 服务器ip:3000
扫完之后,稍等几秒,点击查询登录状态