当前位置: 首页 > news >正文

【日常】爬虫技巧进阶: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 selfdef 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.cnimport os
import time
import loggingfrom 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 ActionChainsdef 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 dataif 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 driverdef 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.cnimport os
import re
import time
import logging
import requestsfrom bs4 import BeautifulSoup
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditionsclass BaseCrawler:tag_regex = re.compile(r"<[^>]+>|\n|\t")global_timeout = 60global_interval = 300chrome_user_data_path = r"C:\Users\caoyang\AppData\Local\Google\Chrome\User Data"# Convert request headers copied from Firefox to dictionary@classmethoddef 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@classmethoddef 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, POSTdef easy_requests(self, method, url, **kwargs):while True:try:response = requests.request(method, url, **kwargs)breakexcept 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 driverdef 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 driverdef _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 dataif 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 driverdef _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 driverdef get_cookies(self, url, driver=None, browser="chrome"):quit_flag = Falseif driver is None:# If there is no driver passedquit_flag = Truedriver = 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 renderedself.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-1"], timeout=60)	# Check if send button is renderedself.check_element_by_xpath(driver, xpath=self.layout_xpaths["send-button-2"], timeout=60)	# Check if send button is rendered# Delete old talktry:driver.find_element_by_xpath(self.layout_xpaths["delete-session"]).click()logging.info("Delete old talk ...")except:logging.info("No old talk found ...")# Requestlogging.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 promptlogging.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 ontry:driver.find_element_by_xpath(self.layout_xpaths["send-button-1"]).click()		# Click on the button to send the promptlogging.info("Use send button 1 ...")breakexcept:try:driver.find_element_by_xpath(self.layout_xpaths["send-button-2"]).click()	# Click on the button to send the promptlogging.info("Use send button 2 ...")breakexcept:logging.info("Use send button error ...")raise Exception("Use send button error ...")# Wait for responseself.check_element_by_xpath(driver, xpath=self.layout_xpaths["chat-area"], timeout=30)	# Check if chat area is renderedself.check_element_by_xpath(driver, xpath=self.layout_xpaths["human-box"], timeout=30)	# Check if human chat box is renderedself.check_element_by_xpath(driver, xpath=self.layout_xpaths["ai-box"], timeout=30)		# Check if AI chat box is renderedfinish_flag = True	# Indicating if AI generation is finishedwhile finish_flag:try:# If like or dislike appear, then stopdriver.find_element_by_xpath(self.layout_xpaths["like-or-dislike-area"])finish_flag = Falseexcept: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 elementai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")										# Get inner HTML of the elementresponse = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ").replace('\n', '\\n')	# Process response textforbidden_flags = [forbidden_string in response for forbidden_string in self.forbidden_strings]if sum(forbidden_flags) > 0:# It indicates that a forbidden string occursfinish_flag = False# Extract AI response textai_box_text = driver.find_element_by_xpath(self.layout_xpaths["ai-box-text"])	# Find AI response text elementai_box_text_inner_html = ai_box_text.get_attribute("innerHTML")					# Get inner HTML of the elementresponse = self.tag_regex.sub(str(), ai_box_text_inner_html).strip("\n\t ")		# Process response textreturn 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_xpathsdef 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多岁的慧悦姐称自己在工作中很闷骚时,觉得很不可思议。跟宝玺姐一起工作的三个月里,也看到她身上很多看似矛盾、但细想又很合理的事情。人类就是这样多态的实例化对象,也注定是多变的非常量实体。

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

罢了。

相关文章:

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

序言 记录一个近期困扰了一些时间的问题。 我很喜欢在爬虫中遇到问题&#xff0c;因为这意味着在这个看似简单的事情里还是有很多值得去探索的新东西。其实本身爬虫也是随着前后端技术的不断更新在进步的。 文章目录 序言Preliminary1 问题缘起1.1 Selenium长文本输入阻塞1.2…...

C++知识点总结(6):高精度乘法真题代码

一、高精度数 低精度数 #include <iostream> #include <cstring> using namespace std;int main() {// 存储并输入两个数字 char a_str[1005] {};long long b;cin >> a_str >> b;// 特例先行&#xff1a;结果是0的情况if (a 0 || b 0){cout <&…...

Polygon zkEVM的Dragon Fruit和Inca Berry升级

1. Polygon zkEVM的Dragon Fruit升级 2023年8月31日&#xff0c;Polygon zkEVM团队宣称启动了其Mainnet Beta的Dragon Fruit升级的10天timelock&#xff0c;预计将于2023年9月11日激活。 Dragon Fruit升级点有&#xff1a; 改进了网络支持了最新的以太坊opcode——PUSH0 1.…...

【计算机网络学习之路】网络基础1

文章目录 前言一. 计算机网络发展局域网和广域网 二. 网络协议三. OSI七层模型四. TCP/IP四层&#xff08;五层&#xff09;模型五. 计算机体系结构与网络协议栈六. 协议形式及局域网通信数据包封装与分用 七. 跨网络通信八. MAC地址与网络通信的理解结束语 前言 本系列文章是…...

HTTP/2.0协议详解

前言 HTTP/2.0&#xff1a;互联网通信的革新标准 随着互联网技术的飞速发展&#xff0c;HTTP协议作为互联网应用最广泛的通信协议&#xff0c;也在不断演进和优化。HTTP/2.0是HTTP协议的最新版本&#xff0c;它旨在提供更高效、更安全、更快速的互联网连接。 一、HTTP/2.0的优…...

Python中的Random模块详解:生成随机数与高级应用

在Python编程中&#xff0c;随机数生成是许多应用的基础之一。random模块为我们提供了生成伪随机数的丰富工具&#xff0c;从简单的随机数生成到复杂的应用场景&#xff0c;都有很多功能可以探索。本文将深入介绍random模块的各个方面&#xff0c;通过详实的示例代码&#xff0…...

(论文阅读32/100)Flowing convnets for human pose estimation in videos

32.文献阅读笔记 简介 题目 Flowing convnets for human pose estimation in videos 作者 Tomas Pfister, James Charles, and Andrew Zisserman, ICCV, 2015. 原文链接 https://arxiv.org/pdf/1506.02897.pdf 关键词 Human Pose Estimation in Videos 研究问题 视频…...

【设计一个缓存--针对各种类型的缓存】

设计一个缓存--针对各种类型的缓存 1. 设计顶层接口2. 设计抽象类 -- AbstractCacheManager3. 具体子类3.1 -- AlertRuleItemExpCacheManager3.2 -- AlertRuleItemSrcCacheManager 4. 类图关系 1. 设计顶层接口 // 定义为一个泛型接口,提供给抽象类使用 public interface Cach…...

Django部署时静态文件配置的坑

Django部署时静态文件配置配置的坑 近期有个需求是用django进行开发部署&#xff0c;结果发现静态文件配置的坑是真的多&#xff0c;另外网上很多的内容也讲不清楚原理&#xff0c;就是这样这样&#xff0c;又那样那样&#xff0c;进了不少坑&#xff0c;这里记录一下关于css,…...

Android---网络编程优化

网络请求操作是一个 App 的重要组成部分&#xff0c;程序大多数问题都是和网络请求有关。使用 OkHttp 框架后&#xff0c;可以通过 EventListener 来查看一次网络请求的详细情况。一次完整的网络请求会包含以下几个步骤。 也就是说&#xff0c;一次网络请求的操作是从 DNS 解析…...

《算法通关村——不简单的字符串转换问题》

《算法通关村——不简单的字符串转换问题》 8. 字符串转换整数 (atoi) 请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入…...

给VSCode插上一双AI的翅膀

#AI编程助手哪家好&#xff1f;DevChat“真”好用# 文章目录 前言一、安装DevChat1.1、访问地址1.2、注册1.3、在VSCode里安装DevChat插件1.3.1、未安装状态1.3.2、已安装状态 二、设置Access Key2.1. 点击左下角管理&#xff08;“齿轮”图标&#xff09;—命令面板&#xff…...

2023年亚太杯数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 建模资料 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常…...

机器学习的医疗乳腺癌数据的乳腺癌疾病预测

项目视频讲解:基于机器学习的医疗乳腺癌数据的乳腺癌疾病预测 完整代码数据分享_哔哩哔哩_bilibili 效果演示: 代码: #第一步!导入我们需要的工具 import numpy as np import pandas as pd import matplotlib.pyplot as plt import seaborn as sns %matplotlib inlin…...

解析:什么是生成式AI?与其他类型的AI有何不同?

原创 | 文 BFT机器人 快速浏览一下头条新闻&#xff0c;你会发现生成式AI似乎无处不在。事实上&#xff0c;一些新闻标题甚至可能是通过生成式AI编写的&#xff0c;例如OpenAI旗下的ChatGPT&#xff0c;这个聊天机器人已经展现出了生成看起来像人类所写文本的惊人能力。 当人们…...

国产化项目改造:使用达梦数据库和东方通组件部署,前后端分离框架

前提&#xff1a;前后端分离前后端包都要用war包。 1、springboot后端改变war包 pom文件添加 <packaging>war</packaging>添加依赖&#xff0c;并且支持tomcat<!-- war包 --><dependency><groupId>org.springframework.boot</groupId><…...

Nginx实现负载均衡

Nginx实现负载均衡 负载均衡的作用 1、解决单点故障&#xff0c;让web服务器构成一个集群 2、将请求平均下发给后端的web服务器 负载均衡的软硬件介绍 负载均衡软件&#xff1a; # nginx 四层负载均衡&#xff1a;stream&#xff08;nginx 1.9版本以后有stream模块&#x…...

SpringCloud 2022有哪些变化

目录 前提条件 AOT支持 Spring Native支持 前提条件 Spring Cloud 2022.0.0是构建在Spring Framework 6.0和Spring Boot 3.0 之上的一S个主要版本。 JDK要求最低需要是Java 17J2EE要求最低需要Jakarta EE 9 AOT支持 Spring cloud 2022支持AOT编译&#xff0c;它是将程序源…...

如何快速本地搭建悟空CRM结合内网穿透工具高效远程办公

&#x1f308;个人主页&#xff1a;聆风吟 &#x1f525;系列专栏&#xff1a;数据结构、Cpolar杂谈 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 &#x1f4cb;前言一. 无需公网IP&#xff0c;使用cpolar实现悟空CRM远程访问二. 通过公网来访问公司…...

Docker打包Python项目

1. 简介 Docker是一种开源的容器化平台&#xff0c;可以将应用程序及其依赖项打包到一个轻量级、可移植的容器中。通过使用Docker&#xff0c;可以简化Python项目的部署和运行&#xff0c;提高开发效率和应用程序的可移植性。 本文将介绍如何使用Docker来打包Python项目。我们…...

【Java并发编程一】并发与并行

为什么引入并发 摩尔定理逐渐失效&#xff0c;单核性能很难提升&#xff0c;通过组合多核性能来进一步满足实际需要&#xff0c;从而引入并发编程。在大部分场景下&#xff0c;并行是由于串行的&#xff0c;并行可以优化非关键节点的时间消耗。 并发的三大特性 原子性  某个…...

MFC/QT 一些快忘记的细节:

1&#xff1a;企业应用中&#xff0c;MFC平台除了用常见的对话框模式还有一种常用的就是单文档模式&#xff0c; 维护别人的代码&#xff0c;不容易区分,看它与程预序认同名cpp&#xff0c;就知道了&#xff0c;比如项目名称为 DoCMFCDemo&#xff0c;那么就看BOOL CDocMFCDe…...

在服务器上部署MVC 6应用程序

在服务器上成功部署MVC 6应用程序&#xff08;现在更为称为ASP.NET Core MVC&#xff09;涉及一系列步骤。以下是一般的指导步骤&#xff1a; 1. 准备服务器环境&#xff1a; - 确保服务器上安装了.NET Core Runtime和.NET Core SDK。可以从[.NET下载页面](https://dotnet.mi…...

golang学习笔记——斐波纳契数列

斐波纳契数列 编写一个程序来计算某个数字的斐波纳契数列。 斐波那契数列是一个数字列表&#xff0c;其中每个数字是前两个斐波那契数字之和。 例如&#xff0c;数字 6 的序列是 1,1,2,3,5,8&#xff0c;数字 7 的序列是 1,1,2,3,5,8,13&#xff0c;数字 8 的序列是 1,1,2,3,5…...

学习raft协议(1)

CAP C: 一致性 强调数据的正确性&#xff0c;每次读操作&#xff0c;要么读到最新&#xff0c;要么读失败 A:可用性 不发生错误&#xff0c;也不能出现过长的等待时间. P:分区容错性 在网络环境不可靠的背景下&#xff0c;整个系统仍然是正常运作的两种流派 &#xff08;1&am…...

SpringSecurity+jwt使用

参考文章链接 自定义SpringSecurity用户 package com.daben.springsecurityjwt.vo;import com.daben.springsecurityjwt.entity.SysUser; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.User; import j…...

html-网站菜单-点击显示导航栏

一、效果图 1.点击显示菜单栏&#xff0c;点击x号关闭&#xff1b; 2.点击一级菜单&#xff0c;展开显示二级&#xff0c;并且加号变为减号&#xff1b; 3.点击其他一级导航&#xff0c;自动收起展开的导航。 二、代码实现 <!DOCTYPE html> <html><head>&…...

【C++函数的进化】函数指针,模板,仿函数,lambda表达式

/*** poject * author jUicE_g2R(qq:3406291309)* file C函数的进化* * language C* EDA Base on VS2022* editor Obsidian&#xff08;黑曜石笔记软件&#xff09;* * copyright 2023* COPYRIGHT 原创学习笔记&#xff1a;转载需获得博…...

云服务器windows service2022 部署git服务器

1 安装 下载地址gitblit 解压到你的一个目录,我这里给的是C:\gitblit 根据官网提示要下载jre or jdk7.0,这里建议使用下载jre (jdk 有时候运行出问题,或者2个都安装),自行安装java,这里不做环境配置的说明 进入c:\gitblit\data 目录里面找到,defaults.properties 文件,编辑主…...

Linux_Docker修改Docker Root Dir

今天遇到需求&#xff0c;要修改一下docker容器和镜像的存储位置&#xff0c;默认位置为/var/lib/docker目录下&#xff0c;要修改到/new/dockerFile目录下。 停止docker服务 sudo service docker stop 备份docker容器镜像 移动/var/lib/docker目录下的文件到/dockerFile目录…...

解决requests 2.28.x版本SSL错误:证书验证失败

1、问题背景 在使用requests 2.28.1版本时&#xff0c;我进行HTTP post传输报告负载时&#xff0c;由于SSL验证设置为True&#xff0c;请求失败&#xff0c;错误如下&#xff1a;(Caused by SSLError(SSLCertVerificationError(1, ‘[SSL: CERTIFICATE_VERIFY_FAILED] certifi…...

【开源】基于Vue.js的开放实验室管理系统的设计和实现

项目编号&#xff1a; S 013 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S013&#xff0c;文末获取源码。} 项目编号&#xff1a;S013&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 实验室类型模块2.2 实验室模块2.3 实…...

使用composer安装ffmpeg的步骤

以下是使用composer安装ffmpeg的步骤&#xff1a; 1.在laravel根目录下执行以下命令安装ffmpeg&#xff1a; composer require php-ffmpeg/php-ffmpeg 2.如果不指定版本号&#xff0c;则默认使用0.14版本。 3.执行以上命令后&#xff0c;composer会自动下载并安装ffmpeg。 …...

RT-DETR优化策略:轻量级Backbone改进 | 高效模型 (Efficient MOdel, EMO),现代倒残差移动模块设计|ICCV2023

🚀🚀🚀本文改进:面向移动端的轻量化网络模型——EMO,它能够以相对较低的参数和 FLOPs 超越了基于 CNN/Transformer 的 SOTA 模型,支持四个版本EMO_1M, EMO_2M, EMO_5M, EMO_6M,参数量如下,相对于自带的rtdetr-l、rtdetr-x有很大提升 layersparametersgradientsEMO_1…...

一些nginx命令

1.停止nginx nginx -s quit systemctl stop nginx.service 立即停止 nginx-s stop 杀死nginx进程 killall nginx 2.启动命令 nginx systemctl start nginx.service 3.查看nginx进程 ps aux | grep nginx 4.重启nginx服务 systemctl restart nginx.service 5.重载…...

WPF自定义控件介绍

在WPF中&#xff0c;自定义控件通常是指从头开始创建一个新控件或从现有控件继承并扩展其功能。自定义控件与用户控件&#xff08;User Control&#xff09;不同&#xff0c;用户控件是通过组合其他控件来构建的&#xff0c;而自定义控件通常涉及对控件的更底层的渲染和行为进行…...

JUNIT使用和注意、以及断言的介绍使用、SpringBoot Test测试类的使用、maven配置使用junit详细介绍

参考文章&#xff1a; https://www.cnblogs.com/zhukaile/p/14514238.html&#xff0c;https://blog.csdn.net/qq_36448800/article/details/126438339 一、什么是单元测试 在平时的开发当中&#xff0c;一个项目往往包含了大量的方法&#xff0c;可能有成千上万个。如何去保…...

强化学习在文生图中的应用:Training Diffusion Models with Reinforcement Learning

论文链接:Training Diffusion Models with Reinforcement Learning项目地址:Training Diffusion Models with Reinforcement Learning官方代码:https://github.com/kvablack/ddpo-pytorch/tree/maintrl实现:https://huggingface.co/docs/trl/ddpo_trainer🤗关注公众号 fu…...

【C语言】数组下标为啥从0开始?下标越界访问一定报错吗?

本篇文章目录 0. 相关文章1. 下标从0开始问题2. 数组下标越界不报错问题 0. 相关文章 指针与指针变量数组名不是首元素地址的的2个例外拨开指针和数组名之间的迷雾 1. 下标从0开始问题 原因是&#xff1a;数组下标访问本质是“指针解引用操作”&#xff0c;而指针又是地址&am…...

机器学习-搜索技术:从技术发展到应用实战的全面指南

在本文中&#xff0c;我们全面探讨了人工智能中搜索技术的发展&#xff0c;从基础算法如DFS和BFS&#xff0c;到高级搜索技术如CSP和优化问题的解决方案&#xff0c;进而探索了机器学习与搜索的融合&#xff0c;最后展望了未来的趋势和挑战&#xff0c;提供了对AI搜索技术深刻的…...

Axelar、J.P.Morgan Onyx、Apollo 完成概念验证,向跨区块链自动化投资领域探索

J.P.Morgan Onyx、Apollo、Axelar、Oasis Pro 以及 Provenance Block Chain 展开合作&#xff0c;共同进行互操作性概念验证&#xff08;Proof-of-Concept&#xff0c;PoC)。 新加坡 — Axelar Inc.、Oasis Pro 、Provenance Blockchain 与 J.P.Morgan Onyx 以及 Apollo 通过新…...

wpf devexpress添加TreeListControl到项目

此教程示范如何添加TreeListControl到项目和绑定控件自引用数据源&#xff1a; 添加数据模型 绑定tree&#xff0c;并添加如下字段到数据源对象&#xff1a; Key字段包含唯一值索引节点 Parent字段包含父索引节点 添加数据模型&#xff08;Employee和Staff类&#xff09;到…...

WPF创建自定义控件编译通过但是找不到资源

报错&#xff1a; 原因: 路径写错了&#xff1a; 不是这样&#xff1a; Source"pack://application:,,,/Controls/Styles/xTabControl.xaml" 而是这样&#xff1a; Source"pack://application:,,,/项目名;component/Controls/Styles/xTabControl.xaml …...

PHP 中传值与传引用的区别,什么时候传值什么时候传引用?

传值&#xff1a;当使用传值的方式时&#xff0c;函数或方法会创建原始变量的一个副本&#xff0c;并将该副本传递给函数或方法。在函数或方法内部&#xff0c;对副本的任何修改都不会影响到原始变量。当函数或方法执行完毕后&#xff0c;副本被销毁&#xff0c;不再使用。 传引…...

es安装方式

es安装方式 1.下载镜像的方式 分词器 kibana和es和容器互通的方式 docker network create es-net开始拉去镜像的方式 docker pull kibana:7.12.1运行镜像的方式 docker run -d \--name es \-e "ES_JAVA_OPTS-Xms512m -Xmx512m" \-e "discovery.typesingle-…...

苍穹外卖项目笔记(2)

1 Nginx 反向代理和负载均衡 1.1 概念 【Tips】可以看到前端请求地址和后端接口地址并不匹配&#xff0c;这里涉及到 nginx 反向代理 &#xff0c;就是将前端发送的动态请求由 nginx 转发到后端服务器 使用 nginx 作反向代理的好处&#xff1a; 提高访问速度&#xff08;在请…...

hive更改表结构的时候报错

现象 FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compete_company_id in alter table statement 23/11/14 17:59:27 ERROR org.apache.hadoop.hive.ql.Driver: FAILED: ParseException line 1:48 cannot recognize input near ADD COLUMN compe…...

redis运维(六)redis-cli命令

一 redis-cli 注意&#xff1a; redis-cli核redis-server版本必须适配 --> 见 redis-cli --version提示&#xff1a; 不过一般安装服务端 redis-server 时内置了客户端 redis-cli说明&#xff1a; redis-cli 是 redis 的一种命令行的客户端工具备注&#xff1a; redis-se…...

JDK1.8 新特性(二)【Stream 流】

前言 上节我们学了 lambda 表达式&#xff0c;很快我就在 Flink 的学习中用到了&#xff0c;我学的是 Java 版本的 Flink&#xff0c;一开始会以为代码会很复杂&#xff0c;但事实上 Flink 中很多地方都用到了 函数接口&#xff0c;这也让我们在编写 Flink 程序的时候可以使用 …...

阿里云CentOS主机开启ipv6

目录 一、云主机开启和使用 ipv6 1、网络和交换机开启 ipv6 2、创建 / 编辑云主机&#xff0c;开启ipv6 3、安全组放行ipv6端口 二、使用 ipv6 地址进行 ssh 连接 三、ipv6 地址绑定域名 一、云主机开启和使用 ipv6 1、网络和交换机开启 ipv6 进入网络、交换机详情页面…...