[Feelings & Sharing] 支付宝账单爬虫,有需要的来
Tofloor
poster avatar
heisen
deepin
2021-09-11 00:01
Author

如果您是站长,如果您的网站需要支付模块,那你一定懂我在说什么。

爬虫使用Glang编写,采用了chromedp库,运行依赖于chrome/chromium(运行时不需要安装桌面环境,最小化系统即可运行,使用浏览器无头模式)。

喜欢请支持,给个评论再走。

看图:

 

上代码:

package main

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"image"
	"log"
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/chromedp"
	"github.com/makiuchi-d/gozxing"
	"github.com/makiuchi-d/gozxing/qrcode"
	goQrcode "github.com/skip2/go-qrcode"
)

var (
	timeOut int = 60  // 响应超时时间
	live    int = 500 // 保活刷新频率
)

func main() {
	// chromdp依赖context上限传递参数
	ctx, _ := chromedp.NewExecAllocator(
		context.Background(),

		// 以默认配置的数组为基础,覆写headless参数
		// 当然也可以根据自己的需要进行修改,这个flag是浏览器的设置
		append(
			chromedp.DefaultExecAllocatorOptions[:],
			//chromedp.Flag("headless", false),
		)...,
	)

	// 创建新的chromedp上下文对象,超时时间的设置不分先后
	// 注意第二个返回的参数是cancel(),只是我省略了
	ctx, _ = context.WithTimeout(ctx, time.Duration(timeOut)*time.Second)
	ctx, _ = chromedp.NewContext(
		ctx,
		// 设置日志方法
		chromedp.WithLogf(log.Printf),
	)

	// 通常可以使用 defer cancel() 去取消
	// 但是在Windows环境下,我们希望程序能顺带关闭掉浏览器
	// 如果不希望浏览器关闭,使用cancel()方法即可
	// defer cancel()
	// defer chromedp.Cancel(ctx)

	// 执行我们自定义的任务 - myTasks函数在第4步
	if err := chromedp.Run(ctx, Tasks()); err != nil {
		log.Print(err)
	}
}

// 开始你的自定义任务
func Tasks() chromedp.ActionFunc {
	// 返回一个函数类型到chromedp.Run
	return func(ctx context.Context) (err error) {

		var loginStat []*cdp.Node
		URL := "https://consumeprod.alipay.com/record/advanced.htm"

		// 打开登录页面
		chromedp.Navigate(URL).Do(ctx)

		// 获取登录二维码,参数2位元素selector,参数二位描述信息,展示在二维码下方
		getCode(ctx, "#J-authcenter > div.authcenter-body.fn-clear", "请使用支付宝客户端扫码登录,")

		// 等待用户扫码,n秒后开始验证
		//chromedp.Sleep(2 * time.Second).Do(ctx)
		time.Sleep(2 * time.Second)

		//确认登录状
		for {
			time.Sleep(1 * time.Second)
			// 检查当前页面是否存在该元素,不存在返回0(检查登录页内容)
			chromedp.Nodes("#J-qrcode-body", &loginStat, chromedp.AtLeast(0)).Do(ctx)

			// 判断是否还停留在登录页面
			if len(loginStat) == 0 {
				fmt.Println("已确认登录")

				// 检查是否需要二次扫码验证
				time.Sleep(3 * time.Second)
				chromedp.Nodes("#hover-instructions > img", &loginStat, chromedp.AtLeast(0)).Do(ctx)

				// 需要二次扫码验证
				if len(loginStat) != 0 {
					getCode(ctx, "#main-containor", "请再次扫码进行安全验证,")

					// 检查二次扫码验证是否通过
					for len(loginStat) != 0 {
						time.Sleep(1 * time.Second)
						chromedp.Nodes("#hover-instructions > img", &loginStat, chromedp.AtLeast(0)).Do(ctx)
					}
				}

				break
			}
		}

		// 判断是否进入账单页面
		time.Sleep(3 * time.Second)
		chromedp.Nodes("#main > div.ui-title.fn-clear > h2", &loginStat, chromedp.AtLeast(0)).Do(ctx)
		if len(loginStat) == 0 {
			return errors.New("error: 登录失败!")
		}

		// 到此已经登录成功,下面进行保活操作
		go alive(ctx)

		//对外提供服务的借口,可以在此使用gin框架搭建web服务对外提供访问
		// 需要注意的是此处代码推出后,保活进程也将推出
		for {
			readBill(ctx, "-96h", false)
			time.Sleep(10 * time.Second)
		}

	}
}

// 读取账单
// pastTime: 查询多少时间内的订单("-24h"), coodplay: 是否只展示二维码收款内容接受bool值
func readBill(ctx context.Context, pastTime string, codePlay bool) {
	var (
		day    string
		times  string
		name   string
		number string
		money  string
		status string
	)

	// 设置正则匹配关键字
	re, _ := regexp.Compile("<.*?>")

	// 更新页面数据
	chromedp.Click("#globalContainer > div.global-common-a > div > div.global-header.fn-clear > div > ul > li.global-nav-item.global-nav-item-current > a").Do(ctx)

	// 开始获取所有关键字
	for id := 10; id > 0; id-- {
		// 检查账单类型,账单名字
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.name > p", &name).Do(ctx)
		name := strings.TrimSpace(re.ReplaceAllString(name, ""))

		// 如果账单类型不是收钱吗首款,则不往下执行,进入下一次循环(变量codePlay参数为true生效)
		if codePlay && name != "收钱码收款" {
			continue
		}

		// 获取指定时间范围内的确切时间
		d, _ := time.ParseDuration(pastTime)
		oldDay := string(time.Now().Add(d).Format("20060102"))
		oldTime := string(time.Now().Add(d).Format("1504"))
		oldDT, _ := strconv.Atoi(oldDay + oldTime)

		// 检查账单日期和时间
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.time > p.time-d", &day).Do(ctx)
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.time > p.time-h.ft-gray", ×).Do(ctx)
		day := strings.TrimSpace(re.ReplaceAllString(day, ""))
		times := strings.TrimSpace(re.ReplaceAllString(times, ""))

		//账单日期和时间转换成数字,用于比较时间范围
		fmtDay := strings.Replace(day, ".", "", -1)
		fmtTimes := strings.Replace(times, ":", "", 1)
		fmtDT, _ := strconv.Atoi(fmtDay + fmtTimes)

		// 如果账单日起不是今天的,则不往下执行,进入下一次循环
		if fmtDT < oldDT {
			continue
		}

		// 查询其他信息
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.tradeNo.ft-gray > p", &number).Do(ctx)
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.amount > span", &money).Do(ctx)
		chromedp.OuterHTML("#J-item-"+fmt.Sprint(id)+" > td.status > p:nth-child(1)", &status).Do(ctx)

		// 截取关键字
		number := strings.TrimSpace(re.ReplaceAllString(number, ""))
		money := strings.TrimSpace(re.ReplaceAllString(money, ""))
		status := strings.TrimSpace(re.ReplaceAllString(status, ""))

		fmt.Println(day, times, name, number, money, status)
	}

	//time.Sleep(time.Duration(live) * time.Second)
	// 重新加载,保活机制
	//chromedp.Click("#globalContainer > div.global-common-a > div > div.global-header.fn-clear > div > ul > li.global-nav-item.global-nav-item-current > a").Do(ctx)
}

// 获取登录二维码
func getCode(ctx context.Context, wbpath string, comment string) (err error) {
	// 0. 等待页面渲染
	time.Sleep(1 * time.Second)

	// 1. 用于存储图片的字节切片
	var code []byte

	// 2. 截图
	// 注意这里需要注明直接使用ID选择器来获取元素(chromedp.ByID)
	if err = chromedp.Screenshot(wbpath, &code).Do(ctx); err != nil {
		return err
	}

	// // 3. 保存图片到文件
	// if err = ioutil.WriteFile("code.png", code, 0755); err != nil {
	// 	return err
	// }

	// 打印二维码 和 通知信息到终端
	printCode(code)
	fmt.Printf("%v 超时时间:%ds\n", comment, timeOut)
	return
}

// 将byte流格式的二维码图片打印到终端
func printCode(code []byte) (err error) {
	// 1. 因为我们的字节流是图像,所以我们需要先解码字节流
	img, _, err := image.Decode(bytes.NewReader(code))
	if err != nil {
		return err
	}

	// 2. 然后使用gozxing库解码图片获取二进制位图
	bmp, err := gozxing.NewBinaryBitmapFromImage(img)
	if err != nil {
		return err
	}

	// 3. 用二进制位图解码获取gozxing的二维码对象
	res, err := qrcode.NewQRCodeReader().Decode(bmp, nil)
	if err != nil {
		return err
	}

	// 4. 用结果来获取go-qrcode对象(注意这里我用了库的别名)
	qr, err := goQrcode.New(res.String(), goQrcode.High)
	if err != nil {
		return
	}
	stringCode := qr.ToSmallString(false)

	// 5. 输出到标准输出流
	fmt.Println(stringCode)

	return
}

func alive(ctx context.Context) {
	for {
		// 重新加载,保活机制
		time.Sleep(time.Duration(live) * time.Second)
		chromedp.Click("#globalContainer > div.global-common-a > div > div.global-header.fn-clear > div > ul > li.global-nav-item.global-nav-item-current > a").Do(ctx)
	}
}
Reply Favorite View the author
All Replies
heisen
deepin
2021-09-11 00:01
#1

占楼

Reply View the author
捕风
deepin
2021-09-14 17:45
#2

Reply View the author
捕风
deepin
2021-09-14 17:45
#3

Reply View the author
捕风
deepin
2021-09-14 17:46
#4

Reply View the author
heisen
deepin
2021-09-23 02:00
#5
捕风

如果还有需要,请尝试使用chrome浏览器打开连接,然后扫码登录,并详细记录您的操作步骤回复我谢谢

https://consumeprod.alipay.com/record/advanced.htm
Reply View the author