豆瓣机器人小组自动回复回帖 Python 源码(持续更新中)

Oria ·
更新时间:2024-09-20
· 589 次阅读

最近给朋友做了一个豆瓣小组自动评论机器人,使用 requests 与 lxml 库,在控制刷新频率的情况下,基本能做到头排评论。除了爬虫的这一部分,还很重要的是要能对帖子回复有趣的内容。GitHub 地址:DouGroupBot 一起讨论:jimxu_work#foxmail.com (把#换成@)

基本功能

同时支持 cookie登录以及账号密码登录。建议尽量使用 cookie,密码登录过多会有验证码阻碍,并有异常行为导致封号的危险。支持自动更新 cookie,总是保持使用的 cookie最新。

通过 XPath 快速解析返回的HTML。有心的朋友可以benchmark一下beautiful soup 和 XPath 表达式之间的速度差异。Python 官方的 XPath 文档其实就很清晰了,推荐一读。在获得服务器返回的HTML和准备好回复内容之间的时间,都是不必要的对用户的延迟,速度很重要。

经过实践调试的最佳动态睡眠,在帖子多的情况下开足马力,帖子少时长时间休眠,并在有新帖出现后快速恢复。同时每次评论后随机睡眠,保证功能的情况下尽量降低服务器请求次数,保护账号,保护阿北。(珍惜db 人人有责)

拥有智能图像识别接口,识别验证码,可接入任何OCR模块。目前知名的开源识别库为 Tesseract,然而并不保证效果。

自动回复

自动回复单独说一下。自动回复的原理就是发出 post 请求。请求的 URL 地址就是帖子的URL加上 add_comment。带不带最后的 #last 无所谓。注意编码问题,如果回复内容来自外部文件,注意转换为 UTF-8 编码。注意 POST 需要带上所有 hidden 的元素的值。不了解的可以看看HTML中关于 form 元素的知识。

抢头排除了速度以外,还需要记录已经评论过的帖子,避免重复评论。这就需要持久化,最开始可以写入 csv 甚至普通 text 文件。要注意一定在评论完之后才写入本地。这里用来区别的key可以直接用帖子的 href。即使把几千条同时载入内存作为字典,也是绰绰有余的。

帖子的回复内容也很关键,毕竟这不是单纯的爬虫程序,是一个有感情的 bot。至少,要有自己的语录库。更加高级有针对性的自然语言处理聊天是进阶的功能。

经验总结 requests 的 Session 相当好用,可以自动长连接、自动更新 cookie。 session 不足在于,对于请求没有重试的功能。是的你没看错。当网络环境差的时候,会频繁遇到来自底层 urllib 形形色色的报错,requests 统一将其抽象为 requests.ConnectionError 。建议从一开始就在项目中使用自己对 requests.Session 的封装,对每次请求封装重试逻辑,并且实现为单例模式。 注意隐私保护,不要将密码上传至 GayHub GitHub。 一开始就有意识地模块化,不需要很详细,毕竟提前优化是万恶之源,但是一定的模块化的确是递增式扩展程序的必经之路。

一起讨论:jimxu_work#foxmail.com (把#换成@)

免责声明

谨慎使用,遵守网站规定与法律法规,对造成任何后果概不负责。请爱惜网站服务器,也是在爱惜自己的豆瓣账号。

主体代码如下(更多在开头的代码仓库):

import requests from lxml import etree import time import random from queue import SimpleQueue, Empty from util import DouUtil from actions import RespGen from mySelectors import NewPostSelector from util import requestsWrapper log = DouUtil.log def get_headers(fileName=None): name = 'headers.txt' if (fileName is not None): name = fileName name = 'resources/' + name headers = {} with open(name, "r", encoding='utf-8') as f_headers: hdrs = f_headers.readlines() for line in hdrs: key, value = line.split(": ") headers[key] = value.strip() return headers def login(url, pwd, userName, session): loginData = {'ck': '', 'name': userName, 'password': pwd, 'remember': 'true'} loginHeaders = get_headers('login_headers.txt') l = session.post(url, data=loginData, headers=loginHeaders) if l.status_code == requests.codes['ok'] or l.status_code == requests.codes['found']: print("Login Successfully") return True else: print("Failed to Login") log.error("Failed to Login", l.status_code) session.close() return False def composeCmnt(session, response): cmntForm = {'ck': '', 'rv_comment': response['ans'], 'start': 0, 'submit_btn': '发送'} cmntForm['ck'] = DouUtil.getCkFromCookies(session) return cmntForm def prepareCaptcha(data, session, postUrl, r=None) -> dict: pic_url, pic_id = DouUtil.getCaptchaInfo(session, postUrl, r) verifyCode = '' pic_path = DouUtil.save_pic_to_disk(pic_url, session) log.debug(pic_url, pic_path) verifyCode = DouUtil.getTextFromPic(pic_path) return data def postCmnt(session, postUrl, request, response): data = composeCmnt(session._session, response) cmntUrl = postUrl + 'add_comment' r = session.post(cmntUrl, data=data, headers={'Referer': postUrl}, files=response.get('files')) # r = session.get(postUrl) code = str(r.status_code) if (code.startswith('4') or code.startswith('5')) and not code.startswith('404'): log.error(r.status_code) raise Exception elif 0 != len(etree.HTML(r.text).xpath("")): log.warning(r.status_code) data = prepareCaptcha(data, session, postUrl, r) r = session.post(cmntUrl, data=data) retry = 1 while 0 != len(etree.HTML(r.text).xpath("")): if retry <= 0: retry -= 1 break data = prepareCaptcha(data, session, postUrl, r) r = session.post(cmntUrl, data=data) retry -= 1 if retry 0: tup = q.get(timeout=3) question, postUrl, dajie = tup[0], tup[1], tup[2] resp = respGen.getResp(question, dajie) postCmnt(reqWrapper, postUrl, question, resp) sleepCmnt = random.randint(20, 30) log.debug("sleep cmnt: ", sleepCmnt) recorder.write(postUrl.split('/')[5] + '\n') record = question + ': ' + resp['ans'] + '\n' file.write(record) except Empty: log.info("Emptied q, one round finished") finally: file.close() recorder.close() DouUtil.flushCookies(s) if __name__ == '__main__': main() 进阶 TODO 多线程 完全的生产者-消费者模式 接入自然语言处理接口 接入OCR接口
作者:永不宕机Java仔



更新 自动 豆瓣 机器人 源码 Python

需要 登录 后方可回复, 如果你还没有账号请 注册新账号