使用python selenium 代码在豆瓣发回复顶贴

写在前面的话:

之前写过在豆瓣上更新旧贴和回复顶贴的脚本. 毕竟已经是几年前的事情了, 一方面N多功能不能用了.,另一方面看当时的代码就跟一砣那啥似的. 所以更新了下, 虽然还是很像那啥

下面这个脚本 主要是学习研究技术点使用的, 顺便完成了发新贴回复顶贴两个功能:

直接上码

import os
import sys
import inspect
import arrow
import requests
from requests.cookies import RequestsCookieJar
import math
from datetime import datetime
import json
import time
import pickle
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from lxml import etree
import re
import random

common_dir = os.path.realpath(os.path.abspath(os.path.join(
    os.path.split(inspect.getfile(inspect.currentframe()))[0],
    "../../../")))
if common_dir not in sys.path:
    sys.path.insert(0, common_dir)
import logging
logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')
# 回帖内容集合
comment_list = [
    "为了把自家的房子租出去, 都上劲了",
    "发个回复, 表示房子还在",
    "房子还在, 若房子不在了, 我会删除所有发贴记录, 不留一丝丝痕迹",
    "房子还在, 你我都怕加了骗子微信, 那先私信聊",
    "发个状态, 表示房子还在 ",
    "666",
]
# 标题集合
subject2300 = [
    '合租出租|6号线青年路站周边|南向次卧|月付2300全包|朝阳大悦城|达美中心',
    '合租出租|朝阳大悦城周边|正规南向次卧|非隔断|月付2300全包',
]
subject1000=[
    '合租出租|6号线青年路站周边|单人小暗间|月付1000全包|朝阳大悦城|达美中心',
    '合租出租|朝阳大悦城周边|单人小暗间|月付1000全包',
]

# 内容集合  自定义的内容, 非真实内容
content2300 = [
    """
    【出租,不限男女】 出租南向次卧(非隔断间), 适合情侣或单男单女居住 (抽烟或带宠物请勿扰, 谢谢)
    """,
    """
    价格是2300元每月,全包,可以月付,再也没有其他费用了。

    其他没啥了,随时看房,和我签约,我也在里面住,有事找我, 不用担心跑路啥的,哈哈
    """
]

content1000 = [
    """
    【出租,限男生】 出租单人小暗间, 适合一个男生. (抽烟或带宠物请勿扰, 谢谢)
    """,
    """
    爪机码字不方便,先报电话:17896035021,让我加你微信那就算了吧,因为我已经被很多冒充找房子的骗加微信,加了之后是微商广告啊什用了。

    其他没啥了,随时看房,和我签约,我也在里面住,有事找我, 不用担心跑路啥的,哈哈
    """
]
content_dict = {
    "type1000": content1000,
    "type2300": content2300,
}
subject_dict = {
    "type1000": subject1000,
    "type2300": subject2300,
}

class MainPage():
    """
    页面
    """
    base_url = 'https://www.douban.com/'  # 首页
    accounts_url = 'https://accounts.douban.com/passport/login?source=group' # 登录页面
    group_url = 'https://www.douban.com/group/'  # 我的小组 页面
    publish = 'https://www.douban.com/group/people/188207245/publish'  # 我发布的 列表面


class MainPageLocators():
    """
    页面元素定位器
    """
    # 第一个页面
    usepassword_lable = (By.XPATH, '//*[@id="account"]/div[2]/div[2]/div/div[1]/ul[1]/li[2]')  # 登录页面的 <密码登录> 标签
    username = (By.ID, 'username') # 用户名
    password = (By.ID, 'password')  # 密码
    # signin_btn = (By.XPATH, '//*[@id="account"]/div[2]/div[2]/div/div[2]/div[1]/div[4]/a')  # 登录页面的 <登录豆瓣> 按钮
    signin_btn = (By.LINK_TEXT, '登录豆瓣')  # 登陆页面的 <登录豆瓣> 按钮
    group_lable = (By.LINK_TEXT, '小组')   #   登陆后的 <小组> 标签
    # 进入二级页面
    mygroup_lable = (By.LINK_TEXT, '我的小组主页')  # 新页面的 <我的小组主页> 标签
    ## 进入发起列表页, 进行回复顶贴
    faqi_lable = (By.LINK_TEXT, '发起')  # 新页面导航栏的 <发起> 标签
    element_tbody = (By.XPATH, '//*[@id="content"]/div/div[1]/div[2]/table/tbody')  # 找到 tbody 元素
    element_tr = (By.TAG_NAME, 'tr') # 找到tr元素
    ### 进入三级页面
    reply_textarea = (By.ID, 'last')  # <你的回应>文本框, 可以填写回复的内容
    captcha_image = (By.ID, 'captcha_image')   # 验证码图片
    captcha_textarea = (By.ID, 'captcha_field')   # 验证码的输入框
    submit_btn = (By.NAME, 'submit_btn')  # <发送> 按键
    
    ## 进入 <加入的小组> 页面, 进行发新贴操作
    join_group_lable = (By.LINK_TEXT, '加入的小组')  # 新页面导航栏的 <加入的小组> 标签
    fayan_lable = (By.XPATH, '//*[@id="group-new-topic-bar"]/div[1]/a')  # 新页面导航栏的 <加入的小组> 标签
    subject_textarea = (By.XPATH, '//*[@id="group-editor-root"]/div/div[2]/div[1]/span/textarea')  # 新贴的标题文本框
    tijiao_btn = (By.LINK_TEXT, '提交')  # <提交> 按键

class Douban():
    def __init__(self, *args, **kwargs):
        self.chromedriver='D:\\work\\spider\\chromedriver.exe'
        self.pages = MainPage()
        self.locators = MainPageLocators()
        self.driver = webdriver.Chrome(executable_path=self.chromedriver)
        self.wait = WebDriverWait(self.driver, 10)
        self.headers = {
            "user-agent":'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36',
        }
        self.session = requests.Session()
        self.session.headers = self.headers
        self.session.verify = False
        self.jar = RequestsCookieJar()

    def sign_in(self, url):
        """
        登录
        打开登录网址-> 定位到密码登录标签->输入用户名和密码-> 点击登录 
        返回 self.driver
        """
        self.driver.get(url)
        # 最大化窗口
        # self.driver.maximize_window()
        # 选择帐户密码标签
        account_method = self.wait.until(EC.element_to_be_clickable(self.locators.usepassword_lable)).click()
        # 定位用户名和密码
        username = self.driver.find_element(*self.locators.username)
        username.send_keys('yourname')
        password = self.driver.find_element(*self.locators.password)
        password.send_keys('password')
        # 登录 TODO: 这里可能会有验证码登录的情况 ,暂时未判断出
        self.wait.until(EC.element_to_be_clickable(self.locators.signin_btn)).click()
        return self.driver
    
    def cookies_writeto_local(self):
        """
        将 selenium 获取到的 cookies 写入本地
        """
        self.driver = self.sign_in(self.pages.accounts_url)
        cookies = self.driver.get_cookies()
        with open('doubancookies.txt', 'wb') as f:
            f.write(pickle.dumps(cookies))
        print("Done")
        self.driver.quit()

    def transform_jar(self, cookies=None):
        """
        若没有传入新的cookies,则读本地的cookies文件,
        否则更新本地cookies文件,  
        再 将 selenium 获取到的cookies 转为适合 requests 模块适用的cookies
        """
        self.session.get(self.pages.base_url)  # 这一步是有必要存在的, 
        if cookies is None:
            with open('doubancookies.txt', 'rb') as f:
                r = f.read()
                if r:
                    cookies = pickle.loads(r)
        else:
            with open('doubancookies.txt', 'wb') as f:
                f.write(pickle.dumps(cookies))
        for cookie in cookies:
            self.jar.set(cookie['name'], cookie['value'])
        return self.jar

    def prompt(self):
        """
        提示框 处理, 没有用到
        """
        self.wait.until(expected_conditions.alert_is_present())
        alert = Alert(self.driver)
        alert.send_keys("Selenium")
        alert.accept()
  
    def run(self):
        """
        自动登录 并进入<我的小组主页>
        """
        # 1. 优先使用的方案: 读取本地的cookies,(经验证, 弃用, 原因: 被网站拦截了)
        # with open('doubancookies.txt', 'rb') as f:
        #     r = f.read()
        #     if r:
        #         cookies = pickle.loads(r)
        #     else:
        #         raise ValueError('doubancookies.txt 中没有内容')
        # self.driver.get(self.pages.base_url)
        # for cookie in cookies:
        #     self.driver.add_cookie(cookie)
        # self.driver.get(self.pages.base_url)

        # 2. 备用方案: 模拟登录(在用)
        try:
            self.driver.get(self.pages.accounts_url)
            self.driver.maximize_window()
            self.driver = self.sign_in(self.pages.accounts_url)
            self.wait.until(EC.element_to_be_clickable(self.locators.group_lable)).click()  # 此时会产生一个新的页面
            for handle in self.driver.window_handles:
                self.driver.switch_to_window(handle)
            self.wait.until(EC.element_to_be_clickable(self.locators.mygroup_lable)).click()
            # self.auto_new_topic()
            self.auto_reply()
        except Exception as e:
            print(e)
        finally:
            self.driver.quit()

    def auto_new_topic(self):
    	"""
		自动发新贴, 规则: 以小组人数为权重, 随机选择5个小组发新贴
		"""
        self.wait.until(EC.element_to_be_clickable(self.locators.join_group_lable)).click()
        with open('my_group_info.txt', 'r') as f:
            groups = json.loads(f.read())
        # 以每个小组的人数作为本小组的权重
        weights = [int(g.get('num')) for g in groups]
        # 以权重随机选出5个小组发贴
        groups = random.choices(groups, weights=weights, k=5)
        for g in groups:
            name = g.get('name')
            print(name)
            url = g.get('url')
            self.driver.get(url)
            self.wait.until(EC.element_to_be_clickable(self.locators.fayan_lable)).click()
            # 由于豆瓣的特殊设计, 无法使用xpath等定位到的内容录入的文本框, 但发现页面的焦点是在内容录入的位置,  因此, 采用的焦点激活定位的方法
            content = self.driver.switch_to_active_element()
            # 这随机选择内容和标题进行发新贴
            type = random.choice(list(content_dict.keys()))
            content.send_keys(random.choice(content_dict.get(type)))
            subject = self.driver.find_element(*self.locators.subject_textarea)
            subject.clear()
            subject.send_keys(random.choice(subject_dict.get(type)))
            self.wait.until(EC.element_to_be_clickable(self.locators.tijiao_btn)).click()
            time.sleep(10)

    def auto_reply(self):
        """
        进入已发贴的列表, 根据回复权重, 随机选择4个, 进行自动回复顶贴
        """
        self.wait.until(EC.element_to_be_clickable(self.locators.faqi_lable)).click()
        table_body = self.driver.find_element(*self.locators.element_tbody)
        tr_list = table_body.find_elements(*self.locators.element_tr)
        result = []
        for tr in tr_list:
            ele = {}
            title_url = tr.find_element(By.XPATH, "td[1]/a").get_attribute('href')
            title_name = tr.find_element(By.XPATH, 'td[1]/a').text
            reply_num = tr.find_element(By.XPATH, 'td[2]').text
            closest_reply_time = tr.find_element(By.XPATH, 'td[3]').text
            group_url=tr.find_element(By.XPATH, 'td[4]/a').get_attribute('href')
            ele.setdefault('title_url', title_url)
            ele.setdefault('title_name', title_name)
            ele.setdefault('reply_num', re.sub(r'\s|回应', '', reply_num))
            ele.setdefault('closest_reply_time', closest_reply_time)
            ele.setdefault('group_url', group_url)
            result.append(ele)
        random_choices = random.choices(result, weights=[int(d.get('reply_num')) for d in result], k=4)
        for choice in random_choices:
            url = choice.get('title_url')
            self.driver.get(url)
            reply = self.driver.find_element(*self.locators.reply_textarea)
            reply.clear()
            reply.send_keys(random.choice(comment_list))
            try:
                # 以下两个还未实现, 原因是捕捉验证码图片较为困难.
                test1 = self.wait.until(EC.visibility_of(self.driver.find_element(*self.locators.captcha_image)))  # 判断验证码图片是否存在, 若存在, 则接受键盘录入验证码
                print(test1)
                test2 = self.wait.until(EC.visibility_of(*self.locators.captcha_image))  # 判断验证码图片是否存在, 若存在, 则接受键盘录入验证码
                print(test2)
                captcha = self.driver.find_element(*self.locators.captcha_textarea)  # 
                captcha.send_keys(Keys.RETURN)  # 在这里接受键盘的录入
            except Exception as e:
                print(e)
            self.wait.until(EC.element_to_be_clickable(self.locators.submit_btn)).click()
            time.sleep(10)



def main():
    obj = Douban()
    # obj.cookies_writeto_local()
    obj.run()

if __name__ == "__main__":
    main()

总结技术点:

  1. 依据权重, 随机选择样本,
import random
l = ['a', 'b', 'c']
weights = [1, 2, 3]
res = random.choices(l, weights=weights, k=1)
# c被选出的概率高于a
  1. selenium 元素定位的多种尝试,

  2. 参考学习的文档:
    selenium 官方文档1
    selenium 官方文档2
    参考的selenium 博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值