puppeteer 是浏览器自动化的产品,它可以实现谷歌浏览器的自动化操作。上一篇文章讲解了利用 puppeteer 爬取网页图片,这是 puppeteer 最基本的用法,本文将进一步探索 puppeteer 的高阶玩法:编写脚本实现12306平台的自动化购票。

前置相关文章:Node.js使用puppeteer+axios爬取页面图片链接并下载图片到本地

1.自动登录

自动输入登录账号和密码并点击登录,输入身份证后四位后获取验证码,手动输入验证码后自动点击登录:

// 打开浏览器并设置浏览器为有头模式,并且减慢操作时间为150ms
const browser = await puppeteer.launch({headless: false,slowMo: 150,});
const page = await browser.newPage(); // 创建一个新的页面
await page.goto('https://kyfw.12306.cn/otn/resources/login.html'); // 12306登录页
await page.type('#J-userName', user) // 输入账号
await page.type('#J-password', pass) // 输入密码
await page.click('#J-login'); // 点击立即登录

await page.type('#id_card', id) // 输入身份证后四位
await page.click('#verification_code'); // 点击获取验证码
// 等待手动输入验证码
await page.waitForFunction(() => {
	const captcha = document.querySelector('#code').value; // 获取验证码
	return captcha.length >= 6; // 验证码长度大于等于 6 时继续执行后面代码
});
await page.click('#sureClick'); // 点击确认

2.自动检测有票列车并预定

自动输入出发地、目的地、出发时间后自动点击查询按钮,检测到有票列车后自动预定。此部分为购票核心逻辑所在,如果需要指定车次或座位,需要添加或修改相关代码。如需实现循环检票和抢票的功能,也可自行修改。这里给出的只是最简单的示例代码。

await page.waitForSelector('#link_for_ticket'); // 等待登录成功后出现跳转链接
await page.click('#link_for_ticket'); // 进入选票页面

await page.waitForSelector('#query_ticket'); // 等待页面加载
//填写出发地、目的地、出发时间
await page.evaluate((fromstation, tostation, time) => {
	document.querySelector('#fromStation').value = fromstation;
	document.querySelector('#toStation').value = tostation;
	document.querySelector('#train_date').value = time;
}, fromstation, tostation, time)

// 以下代码可以加上循环检测代码 实现抢票功能
await page.click('#query_ticket'); // 点击查询
// 获取所有的车次信息
await page.$$eval('tr', async trs => {
	for (let i = 0; i < trs.length; i++) {
		let tr = trs[i];
		// 长度 13 即为车次信息
		if (tr.childElementCount == 13) {
			// 筛选出有票的列车  children[3]为二等票  如果需要其他票可以修改此部分
			if (tr.children[3].innerText != '候补' && tr.children[3].innerText != '--') {
				// console.log(tr.children[3].innerText);
				await tr.lastChild.firstChild.click(); // 点击预购车票
				break; // 找到有票的列车立即预购 即所抢车票为当天最早的有票列车
			}
		}
	}
});

3.自动添加乘坐人并锁定车票

自动添加乘坐人,默认只添加第一位,如需多位可自行添加自动点击事件。勾选后自动点击提交订单并确认订单。车票自动锁定。10分钟之内可以使用pc端或移动端查看并进行付款。学生票需要额外添加一步确认操作。

await page.waitForSelector('#normalPassenger_0'); // 等待normalPassenger_0元素加载
await page.click('#normalPassenger_0'); // 勾选第一位乘车人
// await page.click('#dialog_xsertcj_ok'); // 学生票需要点击确认
await page.click('#submitOrder_id'); // 提交订单
await page.click('#qr_submit_id'); // 再次确认

完整代码

const puppeteer = require('puppeteer');
const axios = require('axios');
const user = `您的账号`; 
const pass = `您的密码`; 
const id = `您的身份证后四位`; 
const fromstation = `TNN`; // 出发地 天门南
const tostation = `WHN`; // 目的地 武汉南
const time = `2023-08-03`; // 出发日期  格式 YYYY-MM-DD
(async () => {
	const browser = await puppeteer.launch({
		headless: false,
		slowMo: 150,
	});
	const page = await browser.newPage();
	await page.goto('https://kyfw.12306.cn/otn/resources/login.html');
	await page.type('#J-userName', user)
	await page.type('#J-password', pass)
	await page.click('#J-login');
	await page.type('#id_card', id);
	await page.click('#verification_code');
	await page.waitForFunction(() => {
		const captcha = document.querySelector('#code').value;
		return captcha.length >= 6;
	});
	await page.click('#sureClick');
	await page.waitForSelector('#link_for_ticket');
	await page.click('#link_for_ticket');
	await page.waitForSelector('#query_ticket');
	await page.evaluate((fromstation, tostation, time) => {
		document.querySelector('#fromStation').value = fromstation;
		document.querySelector('#toStation').value = tostation;
		document.querySelector('#train_date').value = time;
	}, fromstation, tostation, time)
	await page.click('#query_ticket');
	await page.$$eval('tr', async trs => {
		for (let i = 0; i < trs.length; i++) {
			let tr = trs[i];
			if (tr.childElementCount == 13) {
				if (tr.children[3].innerText != '候补' && tr.children[3].innerText != '--') {
					await tr.lastChild.firstChild.click();
					break;
				}
			}
		}
	});
	await page.waitForSelector('#normalPassenger_0');
	await page.click('#normalPassenger_0');
	await page.click('#submitOrder_id');
	await page.click('#qr_submit_id');
})()