【日常】爬虫技巧进阶:textarea的value修改与提交问题(以智谱清言为例)

序言

记录一个近期困扰了一些时间的问题。

我很喜欢在爬虫中遇到问题,因为这意味着在这个看似简单的事情里还是有很多值得去探索的新东西。其实本身爬虫也是随着前后端技术的不断更新在进步的。


文章目录

  • 序言
  • Preliminary
  • 1 问题缘起
    • 1.1 Selenium长文本输入阻塞
    • 1.2 ChromeDriver无法输入非BMP字符(如Emoji字符)
  • 2 Javascript修改textarea文本内容不生效
  • 3 笨方法
  • 4 正解
  • 附录:智谱清言(ChatGLM)Selenium测试脚本
  • 后记


Preliminary

本文假定你已经拥有基于ChromeDriver的Selenium脚本开发经验以及基础的前端知识。

1 问题缘起

1.1 Selenium长文本输入阻塞

长文本输入阻塞的问题是Selenium老大难问题,其原因是Selenium的send_keys()函数在输入字符串时,会将字符串分解为每个字符进行处理,具体可见源码(keys_to_typing函数for循环里最后一个else分支):

def send_keys(self, *keys_to_send):
    """
    Sends keys to current focused element.

    :Args:
     - keys_to_send: The keys to send.  Modifier keys constants can be found in the
       'Keys' class.
    """
    typing = keys_to_typing(keys_to_send)
    if self._driver.w3c:
        for key in typing:
            self.key_down(key)
            self.key_up(key)
    else:
        self._actions.append(lambda: self._driver.execute(
            Command.SEND_KEYS_TO_ACTIVE_ELEMENT, {'value': typing}))
    return self
    
def keys_to_typing(value):
    """Processes the values that will be typed in the element."""
    typing = []
    for val in value:
        if isinstance(val, Keys):
            typing.append(val)
        elif isinstance(val, int):
            val = str(val)
            for i in range(len(val)):
                typing.append(val[i])
        else:
            for i in range(len(val)):
                typing.append(val[i])
    return typing

这显然是挺笨重的操作,因为我们正常将长文本复制到<textarea>文本框中并不需要花费很长时间,但是用driver.find_element(By.xxx, xxx).send_keys(...)逐字输入就很容易使得页面阻塞,这里有个隐藏的问题是输入换行符\n有时会触发文本框内容的提交,这个问题相对容易解决,因为<textarea>是闭合标签,输入的文本内容本质上是在<textarea>{文本内容}</textarea>中,因此将send_keys(...)中的换行符\n替换成<br>即可避免。

题外话,源码为什么要这样写一定会有它的道理。仔细想想也不难理解,keys_to_typing函数的参数value并不总是字符串,也可能是键盘的某个键位,比如常用的我们通过send_keys(Key.CONTROL, 'v')实现向输入框中粘贴文本,因此需要分解成各个字符处理。


图1 智谱清言官网👇👇👇
在这里插入图片描述


本文以智谱清言(ChatGLM)官网的<textarea>标签的文本框输入为例(你也可以在任何一个带有<textarea>文本框的网站进行测试),直接向里面粘贴长文本是非常容易的,但是通过浏览器驱动输入长文本则很容易造成阻塞。

下面的代码是基于Chrome浏览器驱动的测试脚本:

  • 需要配置用户数据路径(initialize_chrome_driver函数中的chrome_user_data_path变量),以跳过智谱清言的登录。

  • 目前访问智谱清言官网还有一个问题,就是会在页面加载上卡很久(有一层Loading…的mask层使得页面元素完全无法交互,应该是在验证登录),此时页面标签和元素已经加载出来,因此不能通过简单的element.is_display来确定页面是否可用。这个状态下可以用Selenium向文本框输入文字,但是无法点击提交按钮,可能可以通过selenium.webdriver.support.expected_condition.element_to_be_clickable函数判断元素是否可交互来确定页面是否完全加载。

    这个问题不展开讨论,一般来说很少彻底卡死(报错信息是element not interactable)。

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import os
import time
import logging

from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.common.action_chains import ActionChains


def initialize_chrome_driver(headless, timeout):
	chrome_user_data_path = r"C:\Users\caoyang\AppData\Local\Google\Chrome\User Data"
	chrome_options = webdriver.ChromeOptions()		
	chrome_options.add_argument(f"user-data-dir={chrome_user_data_path}")	# Import user data
	if headless:
		chrome_options.add_argument("--headless")
	driver = webdriver.Chrome(chrome_options=chrome_options)
	driver.set_page_load_timeout(timeout)
	if not headless:
		driver.maximize_window()
	return driver

def check_element_by_xpath(driver, xpath, timeout):
	WebDriverWait(driver, timeout).until(lambda _driver: _driver.find_element_by_xpath(xpath).is_displayed())

# Regular-used XPaths
layout_xpaths = {"input-box"			: "//textarea[@class=\"scroll-display-none\"]",	# XPath of the input box for human
				 "send-button-1"		: "//img[@class=\"enter_icon\"]",				# XPath of the button to send text of input box
				 "send-button-2"		: "//div[@class=\"enter\"]",					# XPath of the button to send text of input box
				 "chat-area"			: "//div[@id=\"session-container\"]",			# XPath of the chat area which contains all the talks (consist of several chat boxes)
				 "human-box"			: "//div[@class=\"pr\"]",						# XPath of human chat box
				 "ai-box"				: "//div[@class=\"answer-content-wrap\"]",		# XPath of AI chat box
				 "ai-box-text"			: "//div[@class=\"markdown-body\"]",			# XPath of the text contained in AI chat box
				 "create-new-button"	: "//div[@class=\"new-session-button\"]",		# XPath of create new talk
				 "like-or-dislike-area"	: "//div[@class=\"interact-operate\"]",			# XPath of div tag contains like and dislike icons
				 "delete-session"		: "//span[@class=\"btn delete\"]",				# XPath of button to delete old talk
				 }

# Initialize
driver = initialize_chrome_driver(headless=False, timeout=60)
driver.get("https://chatglm.cn/main/detail")

check_element_by_xpath(driver, xpath=layout_xpaths["input-box"], timeout=60)		# Check if input box is rendered
check_element_by_xpath(driver, xpath=layout_xpaths["send-button-1"], timeout=60)	# Check if send button is rendered
check_element_by_xpath(driver, xpath=layout_xpaths["send-button-2"], timeout=60)	# Check if send button is rendered

# Delete old talk
try:
	driver.find_element_by_xpath(self.layout_xpaths["delete-session"]).click()
	logging.info("Delete old talk ...")
except:
	logging.info("No old talk found ...")

# Request
logging.info("Prompt ...")
prompt = """这是一段很长的文本!"""	# e.g. prompt = 'x' * 8000

## Method 1: Use `element.send_keys`
driver.find_element_by_xpath(layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt

在上述代码中,prompt字段过长容易导致send_keys(prompt)造成阻塞,以至于影响后续的代码逻辑。


1.2 ChromeDriver无法输入非BMP字符(如Emoji字符)

ChromeDriver无法输入非BMP字符(据说Geckodriver是可行的),具体报错信息为:

selenium.common.exceptions.WebDriverException: Message: unknown error: ChromeDriver only supports characters in the BMP

这个问题就比上面的长文本阻塞要麻烦多了,前者只是需要等待一段时间就能输入完,但这个问题是真的绕不过去。

好在我们有万能的Javascript😋:

# Request
logging.info("Prompt ...")
prompt = '😋'

# ## Method 1: Use `element.send_keys`
# driver.find_element_by_xpath(layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt

## Method 2: Use Javascript with one auguments (Fail)
js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
driver.execute_script(js, prompt)
logging.info("  - Use Javascript to input ...")

注意,这里Selenium不能直接用JQuery的语法使用$("textarea")来定位元素,因为页面上只有一个<textarea>标签。


图2 使用Javascript输入Emoji👇👇👇(大功告成,我们成功地把😋输入到文本框中啦!)
在这里插入图片描述
问题结束了吗?


2 Javascript修改textarea文本内容不生效

然而,问题才刚刚开始,在图2中,只要你点击提交按钮,就会发现页面提示发送内容不能为空!,甚至只要将鼠标移动到文本框中,😋就会消失。

这个问题确实很让人苦恼,笔者搜索很多关于Javascript修改textarea内容不生效的文章,众说纷纭,但是没有一个起作用的。事实上不止是修改<textarea>标签的value,包括innerTextinnerHTMLtextContent都是不起作用的:


图3 测试textarea的value, innerText, innerHTML属性的修改👇👇👇
在这里插入图片描述
控制台的输出信息会发现,单纯修改value,确实可以看到修改的文本出现在界面上,但是实际HTML中并不会显示,点击提交按钮($(".enter_icon").click())也无法发送内容。

如果修改innerTextinnerHTML,HTML中确实出现了值,修改的文本也出现在界面上,但是仍然无法提交,且点击提交按钮后,界面上不再显示文本,但HTML中依然可以看到Hello

某些回答指出,需要进行dispatchEvent,从最后的正解来看,这种回答说对了一半,如下面的代码所示,通过加入elm.dispatchEvent(new Event('change'));,似乎很有道理,但是实际上依然无法进行提交。

# Request
logging.info("Prompt ...")
prompt = 'Hello!'

# ## Method 1: Use `element.send_keys`
# driver.find_element_by_xpath(layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt

# ## Method 2: Use Javascript with one auguments (Fail)
# js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
# driver.execute_script(js, prompt)
# logging.info("  - Use Javascript to input ...")

# Method 3: Use Javascript with event dispatch (Fail)
js = """var elm = arguments[0], txt = arguments[1]; elm.value += txt; elm.dispatchEvent(new Event('change'));"""
element = driver.find_element_by_xpath(self.layout_xpaths["input-box"])
driver.execute_script(js, element, prompt)
logging.info("  - Use Javascript to input ...")

真是糟透了,难道真的没有人遇到跟我一样的问题吗?


3 笨方法

在控制台测了一遍又一遍后,被这离奇消失的<textarea>文本折磨得实在是无能为力,我决定还是用最笨得方法来解决:

  1. 先把需要输入的文本复制到剪贴板上(使用Pyperclip包,使用pip install pyperclip进行安装)
  2. 使用send_keys方法在<textarea>文本框中进行粘贴文本

效果拔群!事实证明,能抓到猫的就是好老鼠,笨方法一下子就解决了这个该死的问题!

# Request
logging.info("Prompt ...")
prompt = 'Hello!'

# ## Method 1: Use `element.send_keys`
# driver.find_element_by_xpath(layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt

# ## Method 2: Use Javascript with one auguments (Fail)
# js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
# driver.execute_script(js, prompt)
# logging.info("  - Use Javascript to input ...")

# # Method 3: Use Javascript with event dispatch (Fail)
# js = """var elm = arguments[0], txt = arguments[1]; elm.value += txt; elm.dispatchEvent(new Event('change'));"""
# element = driver.find_element_by_xpath(self.layout_xpaths["input-box"])
# driver.execute_script(js, element, prompt)
# logging.info("  - Use Javascript to input ...")

# Method 4: Use keyboard operation (Success)
import pyperclip
pyperclip.copy(prompt)
time.sleep(1)
driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(Keys.CONTROL, 'v')
logging.info("  - Use keyboard to input ...")

4 正解

但是我们总归还是要解决这个问题的,不能因为走了歪门邪道就自鸣得意。其实很容易能想到,之所以无法提交Javascript修改的<textarea>文本框内容,肯定是提交按钮没有绑定到修改的内容,仍然是文本框原本的默认值,这当然需要定义事件进行发送。这里我也搜索到一些人发现无法使用Selenium清空<textarea>文本框的内容,但是他们最后还是向用send_keys(Keys.BACK_SPACE)的键盘操作方法进行妥协。

最后是在StackFlow上找到了这个问题的正解:https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-change-or-input-event-in-react-js

For React 16 and React >=15.6

  • Setter .value= is not working as we wanted because React library overrides input value setter but we can call the function directly on the input as context.
var nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
nativeInputValueSetter.call(input, 'react 16 value');
var ev2 = new Event('input', { bubbles: true});
input.dispatchEvent(ev2);
  • For textarea element you should use prototype of HTMLTextAreaElement class.

这是涉及React框架的知识,可是我再也不会回去学习前端了。

最后的正解代码应该是:

# Request
logging.info("Prompt ...")
prompt = 'Hello!'

# ## Method 1: Use `element.send_keys`
# driver.find_element_by_xpath(layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt

# ## Method 2: Use Javascript with one auguments (Fail)
# js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
# driver.execute_script(js, prompt)
# logging.info("  - Use Javascript to input ...")

# # Method 3: Use Javascript with event dispatch (Fail)
# js = """var elm = arguments[0], txt = arguments[1]; elm.value += txt; elm.dispatchEvent(new Event('change'));"""
# element = driver.find_element_by_xpath(self.layout_xpaths["input-box"])
# driver.execute_script(js, element, prompt)
# logging.info("  - Use Javascript to input ...")

# # Method 4: Use keyboard operation (Success)
# import pyperclip
# pyperclip.copy(prompt)
# time.sleep(1)
# driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(Keys.CONTROL, 'v')
# logging.info("  - Use keyboard to input ...")

# Method 5: Use Javascript with DispatchEvent (Success)
js = """var txt = arguments[0];
const textarea = $("textarea");
var nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
nativeTextAreaValueSetter.call(textarea, txt);
const event = new Event("input", {bubbles: true});
textarea.dispatchEvent(event);"""
driver.execute_script(js, prompt)
logging.info("  - Use Javascript to input ...")

附录:智谱清言(ChatGLM)Selenium测试脚本

感谢您阅读到这里,作为本文的结束,附上完整的代码:

# -*- coding: utf-8 -*-
# @author: caoyang
# @email: caoyang@163.sufe.edu.cn

import os
import re
import time
import logging
import requests

from bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions

class BaseCrawler:
	tag_regex = re.compile(r"<[^>]+>|\n|\t")
	global_timeout = 60
	global_interval = 300
	chrome_user_data_path = r"C:\Users\caoyang\AppData\Local\Google\Chrome\User Data"
	# Convert request headers copied from Firefox to dictionary
	@classmethod
	def headers_to_dict(cls, headers: str) -> dict:
		lines = headers.splitlines()
		headers_dict = {}
		for line in lines:
			key, value = line.strip().split(':', 1)
			headers_dict[key.strip()] = value.strip()
		return headers_dict

	# Easy use of WebDriverWait
	@classmethod
	def check_element_by_xpath(cls, driver, xpath, timeout=30):
		WebDriverWait(driver, timeout).until(lambda _driver: _driver.find_element_by_xpath(xpath).is_displayed())
	
	# @param method: e.g. GET, POST
	def easy_requests(self, method, url, **kwargs):
		while True:
			try:
				response = requests.request(method, url, **kwargs)
				break
			except Exception as e:
				logging.warning(f"Error {method} {url}, exception information: {e}")
				logging.warning(f"Wait for {self.global_interval} seconds ...")
				time.sleep(self.global_interval)
		return response

	# Initialize driver
	def initialize_driver(self, browser="chrome", headless=True, timeout=60, **kwargs):
		browser = browser.lower()
		assert browser in ["chrome", "firefox"], f"Unknown browser name: {browser}"
		return eval(f"self._initialize_{browser}_driver")(headless, timeout, **kwargs)
	
	# Initialize Google Chrome driver
	def _initialize_chrome_driver(self, headless, timeout, **kwargs):
		chrome_options = webdriver.ChromeOptions()		
		chrome_options.add_argument(f"user-data-dir={self.chrome_user_data_path}")	# Import user data
		if headless:
			chrome_options.add_argument("--headless")
		driver = webdriver.Chrome(chrome_options=chrome_options)
		driver.set_page_load_timeout(timeout)
		if not headless:
			driver.maximize_window()
		return driver

	# Initialize Mozilla Firefox driver
	def _initialize_firefox_driver(self, headless, timeout, **kwargs):
		options = webdriver.FirefoxOptions()
		if headless:
			options.add_argument("--headless")
		driver = webdriver.Firefox(options=options)
		driver.set_page_load_timeout(timeout)
		if not headless:
			driver.maximize_window()
		return driver

	# Get cookies by driver
	def get_cookies(self, url, driver=None, browser="chrome"):
		quit_flag = False
		if driver is None:
			# If there is no driver passed
			quit_flag = True
			driver = self.initialize_driver(browser=browser, headless=True, timeout=30)
		driver.get(url)
		cookies = driver.get_cookies()
		def _cookie_to_string(_cookies):
			_string = str()
			for _cookie in _cookies:
				_name = _cookie["name"]
				_value = _cookie["value"].replace(' ', '%20') # %20 refers to space char in HTML
				_string += f"{_name}={_value};"
			return _string.strip()
		if quit_flag:
			driver.quit()
		return _cookie_to_string(cookies)


class ChatGLMCrawler(BaseCrawler):
	urls = {"home": "https://chatglm.cn/main/detail",	# Home URL
			}
	layout_xpaths = {"input-box"			: "//textarea[@class=\"scroll-display-none\"]",					# XPath of the input box for human
					 # "input-box"			: "//div[@class=\"input-box-inner\"]",							# XPath of the input box for human (div tag cannot be interacted)
					 "send-button-1"		: "//img[@class=\"enter_icon\"]",								# XPath of the button to send text of input box
					 "send-button-2"		: "//div[@class=\"enter\"]",									# XPath of the button to send text of input box
					 "chat-area"			: "//div[@id=\"session-container\"]",							# XPath of the chat area which contains all the talks (consist of several chat boxes)
					 "human-box"			: "//div[@class=\"pr\"]",										# XPath of human chat box
					 "ai-box"				: "//div[@class=\"answer-content-wrap\"]",						# XPath of AI chat box
					 "ai-box-text"			: "//div[@class=\"markdown-body\"]",							# XPath of the text contained in AI chat box
					 "create-new-button"	: "//div[@class=\"new-session-button\"]",						# XPath of create new talk
					 "like-or-dislike-area"	: "//div[@class=\"interact-operate\"]",							# XPath of div tag contains like and dislike icons
					 "delete-session"		: "//span[@class=\"btn delete\"]",								# XPath of button to delete old talk
					 }
	forbidden_strings = []
	def __init__(self):
		super(ChatGLMCrawler, self).__init__()

	# @param driver		: WebDriver object
	# @param prompt		: The question you would like to ask AI
	# @param model_name	: One of the key in `model_card_xpath`, e.g. "chatgpt3.5(16k)"
	def request(self, driver, prompt, first_trial=True):
		prompt = prompt.replace('\n', "\\n")
		if first_trial:
			driver.get(self.urls["home"])
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["input-box"], timeout=60)		# Check if input box is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-1"], timeout=60)	# Check if send button is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-2"], timeout=60)	# Check if send button is rendered
		# Delete old talk
		try:
			driver.find_element_by_xpath(self.layout_xpaths["delete-session"]).click()
			logging.info("Delete old talk ...")
		except:
			logging.info("No old talk found ...")
		# Request
		logging.info("Prompt ...")
		try:
			## Method 1: Use `element.send_keys`
			driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(prompt)	# Input the given prompt
			logging.info("  - ok!")
		except:
			# ## Method 2: Use Javascript with one auguments (Fail)
			# js = """var txt = arguments[0]; document.getElementsByTagName("textarea")[0].value = txt;"""		
			# driver.execute_script(js, prompt)
			# logging.info("  - Use Javascript to input ...")

			# # Method 3: Use Javascript with event dispatch (Fail)
			# js = """var elm = arguments[0], txt = arguments[1]; elm.value += txt; elm.dispatchEvent(new Event('change'));"""
			# element = driver.find_element_by_xpath(self.layout_xpaths["input-box"])
			# driver.execute_script(js, element, prompt)
			# logging.info("  - Use Javascript to input ...")

			# # Method 4: Use keyboard operation (Success)
			# import pyperclip
			# pyperclip.copy(prompt)
			# time.sleep(1)
			# driver.find_element_by_xpath(self.layout_xpaths["input-box"]).send_keys(Keys.CONTROL, 'v')
			# logging.info("  - Use keyboard to input ...")

			# Method 5: Use Javascript with DispatchEvent (Success)
			js = """var txt = arguments[0];
			const textarea = $("textarea");
			var nativeTextAreaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
			nativeTextAreaValueSetter.call(textarea, txt);
			const event = new Event("input", {bubbles: true});
			textarea.dispatchEvent(event);"""
			driver.execute_script(js, prompt)
			logging.info("  - Use Javascript to input ...")

		while True:
			# The button is dynamic and sometimes fail to click on
			try:
				driver.find_element_by_xpath(self.layout_xpaths["send-button-1"]).click()		# Click on the button to send the prompt
				logging.info("Use send button 1 ...")
				break
			except:
				try:
					driver.find_element_by_xpath(self.layout_xpaths["send-button-2"]).click()	# Click on the button to send the prompt
					logging.info("Use send button 2 ...")
					break
				except:
					logging.info("Use send button error ...")
					raise Exception("Use send button error ...")
		# Wait for response
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["chat-area"], timeout=30)	# Check if chat area is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["human-box"], timeout=30)	# Check if human chat box is rendered
		self.check_element_by_xpath(driver, xpath=self.layout_xpaths["ai-box"], timeout=30)		# Check if AI chat box is rendered
		finish_flag = True	# Indicating if AI generation is finished
		while finish_flag:
			try:
				# If like or dislike appear, then stop
				driver.find_element_by_xpath(self.layout_xpaths["like-or-dislike-area"])
				finish_flag = False
			except:
				ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box-text"])						# Find AI response text element
				# ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box"])							# Find AI response text element
				ai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")										# Get inner HTML of the element
				response = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ").replace('\n', '\\n')	# Process response text
				forbidden_flags = [forbidden_string in response for forbidden_string in self.forbidden_strings]
				if sum(forbidden_flags) > 0:
					# It indicates that a forbidden string occurs
					finish_flag = False
		# Extract AI response text
		ai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box-text"])	# Find AI response text element
		ai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")					# Get inner HTML of the element
		response = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ")		# Process response text
		return response

	# @param data_path: EXCEL file of job descriptions
	# @param save_path: file path for storing AI response
	# @param model_name: defined in model_card_xpaths
	def demo(self, model_name="chatgpt3.5(16k)"):
		driver = self.initialize_driver(browser="chrome", headless=False, timeout=60)
		driver.implicitly_wait(15) # 
		prompt = "给我讲述一下《基督山伯爵》的故事,500字左右。"
		response = self.request(driver, prompt)
		with open(f"d:/answer-chatglm.txt", 'w', encoding="utf8") as f:
			f.write(response)
		time.sleep(5)
		driver.quit()

if __name__ == "__main__":
	crawler = ChatGLMCrawler()
	crawler.demo()

图4 脚本测试截图👇👇👇
在这里插入图片描述


后记

昨天S消防演习,人在18楼,走到10楼就堵得下不去了,宝玺姐根本就没有下楼,一直呆在工位上,我回来后说如果S真的失火,应该会有直升机来营救,我们应该往上面天台跑而不是往楼下逃生。宝玺姐非常认真地跟我说,S如果失火,我们肯定都是死路一条,有一种高知女性特有的决绝感。我不知道SXY是否也是如此,其实我现在离TA很近,可是又离TA很远。

在这次高校百英里接力赛,摸到10km路跑40分钟的大门之后,我坚定了一个信念,人可以办成任何事情,人也不要办成每一件事情。十年前,甚至五年、三年前我都不敢想象有一天能把10km跑进40分钟,但现在确确实实做到了,即便是在这样奔波的生活中。未来我也一定有机会全马破三、完成铁三越野。

现在我觉得,人生是一个等待和追求的过程,迷茫的时候可以静静地等待,明确的时候应当无畏地追求,一切安顿时就该好好休息。我看到不同的人、不同的事、不同的生活、不同的态度,我觉得平等的交流是最难能可贵的事,让每个认知水平不同的人,即便是在一个相对小的群体里,都能够发声,也敢于发声,让每一种声音都有它的容身之处。

我们总是有一种莫名的主观的客观视角,总会觉得有一些自己认为理所当然的事情,别人都应当如此,然而并不其然。当我看到35岁的吉米和乔总吃饭时还在讨论《咒术回战》的五条悟、《进击的巨人》的地鸣,听到儿子都已经10多岁的慧悦姐称自己在工作中很闷骚时,觉得很不可思议。跟宝玺姐一起工作的三个月里,也看到她身上很多看似矛盾、但细想又很合理的事情。人类就是这样多态的实例化对象,也注定是多变的非常量实体。

每次写到这里,都会想起很多遗憾。岁数越长,越觉得人生是很奇特的历程,你永远不知道盒子里下一块巧克力是什么颜色,你甚至无法知晓盒子里到底是不是一块巧克力,或许是根棒棒糖也说不定,就像曾经错过的人和事,在将来的某个节点是否会再次相遇?

罢了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/159546.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

60V100V降压ic推荐

在电源降压领域&#xff0c;一款优秀的降压IC需要具备高效、稳定、安全、易于使用等特性。今天&#xff0c;我们为大家推荐一款具有9.2V至100V输入电压范围、4.5A连续输出电流、96%峰值效率、495μA工作静态电流、峰值电流模式控制、100V19mQ高边和低边MOS、固定150kHz开关频率…

C/C++ 字符 - ‘0‘ 或者 + ‘0‘ 的含义及区别(从ASCII码深度解析,小白一看就懂!!!)

目录 一、前言 二、什么是ACSII码&#xff1f; 三、深度理解字符 - ‘0‘ 或者 ‘0‘ 四、实战演练 五、共勉 一、前言 想必大家在刷题或者是看别人写的代码中&#xff0c;经常会遇到 s[i]-’0‘ 或者 s[i]’0‘ 这个情况&#xff0c;初次遇到这种代码&#xff0c;肯定是…

4.1指令系统-指令格式

现代计算机的结构 计算机的工作过程 指令的定义 指令&#xff08;又称机器指令&#xff09;&#xff1a; 是指示计算机执行某种操作的命令&#xff0c;是计算机运行的最小功能单位。 一台计算机的所有指令的集合构成改机指令系统&#xff0c;也称为指令集。 注意&#xff1…

拼多多百亿补贴商品详情API接口系列

拼多多API接口是拼多多网提供的一种应用程序接口&#xff0c;允许开发者通过程序访问拼多多网站的数据和功能。通过拼多多API接口&#xff0c;开发者可以开发各种应用程序&#xff0c;如店铺管理工具、数据分析工具、购物比价工具等。在本章中&#xff0c;我们将介绍拼多多API接…

Jquery 通过class名称属性,匹配元素

UI自动化过程中&#xff0c;常常需要判断某个元素是否满足条件&#xff0c;再走不通的脚本逻辑&#xff1b;、本文介绍如何通过jquery判断菜单是否展开&#xff0c;来决定是否执行菜单展开脚本&#xff1b;Jquery通过class名称属性&#xff0c;匹配元素 我们先分析&#xff0c;…

HTML5学习系列之项目实战1

HTML5学习系列之项目实战1 前言代码记录问题总结 前言 学习记录 代码 <div id"player"><audio id"musicbox"></audio><div id"controls" class"clearfix controls"><div id"play" class"…

Android 当中的 Fragment 协作解耦方式

Android 当中的 Fragment 协作解耦方式 文章目录 Android 当中的 Fragment 协作解耦方式第一章 前言介绍第01节 遇到的问题第02节 绘图说明 第二章 核心代码第01节 代理人接口第02节 中间人 Activity第03节 开发者A第04节 开发者B第05节 测试类 第一章 前言介绍 第01节 遇到的…

Ubuntu22.04 Apache2安装SSL证书 https

一、免费证书申请 https://help.aliyun.com/zh/ssl-certificate/user-guide/overview-of-free-certificates 得到 三、配置 执行以下命令&#xff0c;打开default-ssl.conf文件。 vim /etc/apache2/sites-available/default-ssl.conf 在default-ssl.conf配置文件中&#xff…

List is a raw type. References to generic type List<E> should be parameterized

List is a raw type. References to generic type List<E> should be parameterized 都是代码习惯问题懒

大型且复杂项目的资源管理怎么做?

职场中&#xff0c;我劝你做个“显眼包”&#xff01;作为天天背锅、踩坑、救火的项目经理&#xff0c;积极响应、随时反馈、成果汇报这些一样都不落下&#xff0c;项目才能顺利开展。这不&#xff0c;项目经理小李就是由于自己过于低调且内敛的性格&#xff0c;差点把项目都做…

影像仪全景导航,快速定位产品特征!

**在工业制造领域中&#xff0c;影像仪全景导航可以提供全景影像&#xff0c;将整个区域的图像精准地捕捉下来&#xff0c;并通过软件算法实现高效处理&#xff0c;以呈现出更加清晰和详细的视图。**这一技术不仅可以提高定位精度&#xff0c;同时还能大幅度提升定位效率。与自…

Pytorch torch.exp()的使用举例

代码实验展示: Microsoft Windows [版本 10.0.18363.1256] (c) 2019 Microsoft Corporation。保留所有权利。C:\Users\chenxuqi>conda activate ssd4pytorch1_2_0(ssd4pytorch1_2_0) C:\Users\chenxuqi>python Python 3.7.7 (default, May 6 2020, 11:45:54) [MSC v.191…

轻量封装WebGPU渲染系统示例<34>-数据驱动之Json构建场景

场景和数据之间的互通&#xff1a; 场景数据化或者数据化场景&#xff0c;是当前的主流场景数据构成方式。方便传输方便交换甚至是交互。 内置数据互通机制更有利于用户在各种应用场合下实现具体的3D相关的应用需求。用户只需要关心标准的或者约定好的数据定义及操作方式就能方…

GPS信号的数字接收处理matlab仿真,包括频率点搜索,捕获跟踪,相关峰检测等步骤

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 频率点搜索 4.2. 捕获跟踪 4.3. 相关峰检测 5.算法完整程序工程 1.算法运行效果图预览 低信噪比下仿真结果如下&#xff1a; 2.算法运行软件版本 matlab2022a 3.部分核心程序 ...…

直接插入排序

一.介绍. 其基本思想为数据元素被已经放入一个已经排好的有序数组中&#xff0c;现插入一个元素进入该数组&#xff0c;按顺序&#xff08;即大小关系&#xff09;放进适当位置&#xff0c;并且其后面的元素都向后移动移位。 如图&#xff1a; 对于上述问题&#xff1a; 代码…

【AI视野·今日Sound 声学论文速览 第三十六期】Mon, 30 Oct 2023

AI视野今日CS.Sound 声学论文速览 Mon, 30 Oct 2023 Totally 7 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers Style Description based Text-to-Speech with Conditional Prosodic Layer Normalization based Diffusion GAN Authors Neeraj Kumar, A…

湖科大计网:网络层

一、网络层概述 一、基本概念 网络层的主要任务是实现网络互连&#xff0c;进而实现数据包在各网络之间传输。 若只有单个网络&#xff0c;只需要物理层和数据链路层即可。 不同的异构网络需要路由器将其互连&#xff0c;路由器的每一个接口代表一个不同的网络&#xff0c;也区…

Windows安装nvm【node.js版本管理工具】

目录 下载安装包 安装 配置 配置node的国内镜像源 配置npm的国内镜像源 常用命令 查看可安装的node版本 安装指定的版本 查看已有的node版本列表 切换版本 下载安装包 https://github.com/coreybutler/nvm-windows/releases/tag/1.1.11 安装 安装过程就不贴了&#xff0…

leetcode刷题日志-14最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff1a;strs [“dog”,“raceca…

Java进阶笔记(面向对象后, 持续更新)

常用API 游戏打包成exe 考虑的因素 要有图形化界面代码要打包起来游戏用到的图片也要打包JDK也要打包 核心步骤 把所有代码打包成一个压缩包, jar后缀的压缩包把jar包转换成exe安装包把第二部的exe, 图片, JDK整合在一起, 变成最终的exe安装包 1. Math 是一个帮助我们用…