程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

使用python selenium 代碼在豆瓣發回復頂貼

編輯:Python

文章目錄

  • 寫在前面的話:
  • 直接上碼
  • 總結技術點:

寫在前面的話:

之前寫過在豆瓣上更新舊貼和回復頂貼的腳本. 畢竟已經是幾年前的事情了, 一方面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 博客


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved