最近给朋友做了一个豆瓣小组自动评论机器人,使用 requests 与 lxml 库,在控制刷新频率的情况下,基本能做到头排评论。除了爬虫的这一部分,还很重要的是要能对帖子回复有趣的内容。

基本功能

同时支持 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。至少,要有自己的语录库。更加高级有针对性的自然语言处理聊天是进阶的功能。

经验总结

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

免责声明

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


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

import requests
from lxml import etree
import time
import random
from queue import SimpleQueue, Emptyfrom util import DouUtil
from actions import RespGen
from mySelectors import NewPostSelector
from util import requestsWrapperlog = DouUtil.logdef get_headers(fileName=None):name = 'headers.txt'if (fileName is not None):name = fileNamename = 'resources/' + nameheaders = {}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 headersdef 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 Trueelse:print("Failed to Login")log.error("Failed to Login", l.status_code)session.close()return Falsedef composeCmnt(session, response):cmntForm = {'ck': '', 'rv_comment': response['ans'],'start': 0, 'submit_btn': '发送'}cmntForm['ck'] = DouUtil.getCkFromCookies(session)return cmntFormdef 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 datadef 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 Exceptionelif 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 = 1while 0 != len(etree.HTML(r.text).xpath("")):if retry <= 0:retry -= 1breakdata = prepareCaptcha(data, session, postUrl, r)r = session.post(cmntUrl, data=data)retry -= 1if retry < 0:log.error(r.status_code)DouUtil.alertUser()pic_url, pic_id = DouUtil.getCaptchaInfo(session, postUrl, r)code = DouUtil.callAdmin(session, pic_url, postUrl)# data.update({'captcha-solution': code, 'captcha-id': pic_id})r = session.post(cmntUrl, data=data)if 0 != len(etree.HTML(r.text).xpath("")):raise Exceptionlog.info("Success.", request + '  --' + data['rv_comment'])else:log.info("Success.", request + '  --' + data['rv_comment'])def main():respGen = RespGen.RespGen()q = SimpleQueue()cred = DouUtil.getCred()pwd = cred['pwd']userName = cred['userName']loginReqUrl = ''# s = requests.Session()reqWrapper = requestsWrapper.ReqWrapper()s = reqWrapper._sessions.headers.update({'Host': 'www.douban.com','Connection': 'keep-alive','User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0','Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accept-Language': 'en-US,en;q=0.5',})s.cookies.update(DouUtil.loadCookies())slctr = NewPostSelector.NewPostSelector(q, reqWrapper)timeToSleep = 5combo = 0while True:q = slctr.select()if q.qsize() == 0:log.debug("sleep fro emty queue: ", timeToSleep)time.sleep(timeToSleep)else:timeToSleep = 5log.info("****selection, q size: ", q.qsize(), "timeToSleep: " + str(timeToSleep) + "****")try:file = open('resources/record.txt', 'a', encoding='utf-8')recorder = open('resources/histo.txt', "a", encoding='utf-8')while q.qsize() > 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接口

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

  1. python二手交易平台代码_PYTHON爬虫实战_垃圾佬闲鱼爬虫转转爬虫数据整合自用二手急速响应捡垃圾平台_3(附源码持续更新)...

    说明 文章首发于HURUWO的博客小站,本平台做同步备份发布. 如有浏览或访问异常图片加载失败或者相关疑问可前往原博客下评论浏览. 原文链接 PYTHON爬虫实战_垃圾佬闲鱼爬虫转转爬虫数据整合自用二 ...

  2. Sublime Text3 注册码激活码(持续更新中2018-6-5)

    原文地址为: Sublime Text3 注册码激活码(持续更新中2018-6-5) Sublime Text 3的注册码 个人记录,便于查找 6月5版本又来了 CSDN下载Sublime Text3 ...

  3. 编写代码、打印图4-2所示的图形python_Python之turtle库画各种有趣的图及源码(更新中)_一个超会写Bug的程序猿的博客-CSDN博客...

    原文作者:一个超会写Bug的安太狼 原文标题:Python之turtle库画各种有趣的图及源码(更新中) 发布时间:2021-02-09 03:35:11 Turtle库是Python语言中一个很流行 ...

  4. Python之turtle库画各种有趣的图及源码(更新中)

    Turtle库是Python语言中一个很流行的绘制图像的函数库,想象一个小乌龟,在一个横轴为x.纵轴为y的坐标系原点,(0,0)位置开始,它根据一组函数指令的控制,在这个平面坐标系中移动,从而在它爬行 ...

  5. html锯齿边框,css3制作邮票(锯齿边框)-css3制作邮票(锯齿边框)-HTML5+CSS3-编玩编学- 注重积累的过程 快速锁定问题解决问题 写博客,在积累源码,持续更新技术博客,加分项...

    css3制作邮票(锯齿边框) html: css: body,h1,h2,h3,h4,ul,li,div,a,p{ margin: 0; padding: 0; font-family: arial; ...

  6. Linux部署Python项目(持续更新中)

    项目部署 ​ 项目部署对恩操作通常是由运维人员来进行统一管理装配的,但是对于一个开发人员来讲,基础的项目部署还是要会的. ​ 这里我主要讲解python的项目是如何部署的,至于项目哪里来的,这就要看观 ...

  7. 官方源、镜像源汇总--持续更新中

    一.站点版 (一).企业站 1.搜狐:http://mirrors.sohu.com/ 2.网易:http://mirrors.163.com/ 3.阿里云:http://mirrors.aliyun ...

  8. [python]队列操作(持续更新中)

    Ctrl+f搜关键词,搜不到找其他博客,下面肯定没有 队列 队列要实现的操作 Queue() 创建一个空的队列 is_empty() 判断一个队列是否为空 length() push(item) 添加 ...

  9. Python源码builtins.py

    为何Python源码builtins.py中的函数体都是pass? 目录 为何Python源码builtins.py中的函数体都是pass? 1.突发奇想看看源代码 2.如何查看源代码? 为何Pyth ...

最新文章

  1. MVC+Ninject+三层架构+代码生成 -- 总结(一、數據庫)
  2. 不管你的x86能不能升级Win 11,反正树莓派能!|安装教程
  3. Devexpress GridControl 设置combobox下拉框
  4. @PropertySource@ImportResource@Bean
  5. 7、Power Query-合并查询
  6. python方格染色_判定二部图的着色算法(Python),题,染色法,判断,二分
  7. 编译c语言程序时 程序中的注释部分将,C语言程序编译时,程序中的注释部分将 答案:不参加编译,也不会出现在目标程序中...
  8. 清华团队研发,首款国产电力电子仿真软件来啦~已捐赠哈工大、海工大、清华使用!...
  9. 内存cgroup---CGroup中参数由来篇
  10. java开发的程序怎么用_java安装后怎么使用?第一次编写java程序
  11. python批量发送邮件_EDM营销算法:python自动批量发邮件
  12. 学习笔记13--基于蜂窝移动通信的车联网技术
  13. Redis 源码解析(8) AOF持久化
  14. 美团点评 2019校园招聘 后台开发方向
  15. Python文件操作-替换srt文件行文本
  16. python中 math.isfinite返回值为false_Python math.isfinite() 方法
  17. iNode客户端“未收到服务器回应,即将强行下线,请检查终端能否正常访问网络或者与管理员联系”问题与解决方式
  18. 3乘3魔方第四步_3乘3魔方的解法都有哪些?(讲具体点,怎么具体呢?有公式步骤吧!)?...
  19. vba给服务器发送消息,我如何发送一个HTTP POST请求使用VBA从Excel服务器?我如何发送一个HTTP POST请...
  20. 搭建个人网站 保姆级教程(一)云服务器购买以及环境部署

热门文章

  1. MySQL存储过程专题
  2. 谷歌云端硬盘直接下载方法
  3. python 字符串对象的使用习题练习一
  4. linux top 命令可视化_top——交互界面使用图解
  5. [北京/全职] Super0 | 超级灵兽重磅招聘
  6. 解析图腾柱无桥PFC的状态控制(基于DSP C2000)
  7. 知识表征的计算机模型,知识表征
  8. 汪涵、刘国梁代言爱钱进遭立案!出借人声讨退还代言费,明星是否要担责?
  9. js layer的使用
  10. 淘宝商品详情采集上架京东店铺(京东商品详情接口,淘宝商品详情接口,无货源商品数据采集接口)接口代码对接教程