<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>万邦api博客</title><link>https://www.shopapi.com.cn/</link><description>Good Luck To You!</description><item><title>Python 实战｜搭建电商商品自动化监控与数据分析小工具，告别手工复制粘贴</title><link>https://www.shopapi.com.cn/?id=241</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品跟踪、价格盯盘、库存监控、市场数据分析；适合商家、运营、数据分析同学直接上手跑通，代码可直接二次改造。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营或者竞品调研的时候，很多人都有这样的痛点： 想要盯一批商品的价格变动、库存余量、规格变化，每次都要手动打开网页一个个复制记录。商品一多，耗时巨大，还很容易错过促销降价、库存清空的关键节点。&lt;/p&gt;&lt;p&gt;如果直接写网页解析，还会遇到频繁的访问限制、页面结构改版、验证码等一堆麻烦，调试就要耗费大量时间。&lt;/p&gt;&lt;p&gt;今天分享一套轻量的实现方案，借助 Open Claw 能力，只需要商品编号，就能拿到完整结构化商品信息；再搭配 Python 脚本，实现定时巡检、异动提醒、历史数据存储、简单统计分析，快速拥有一套属于自己的监控小工具。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;环境依赖：Python3.7+，需要安装 requests、pandas、schedule。&lt;/p&gt;&lt;/blockquote&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22pip%20install%20requests%20pandas%20schedule%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;schedule&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;整体实现思路&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;配置凭证与监控清单：填入自己的访问凭证，录入需要盯的商品编号列表。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E5%AE%9A%E6%97%B6%E6%8B%89%E5%8F%96%E5%95%86%E5%93%81%E5%85%A8%E9%87%8F%E4%BF%A1%E6%81%AF%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22pILHzR-1792477104480%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;定时拉取商品全量信息&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;定时拉取商品全量信息&quot;&gt;定时拉取商品全量信息&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：获取标题、售价、原价、库存、多规格 SKU、店铺、图片等完整信息。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;数据持久化保存：每次采集结果写入 Excel，留存历史记录，方便后续做对比分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;异动检测逻辑：对比上一次记录，价格涨跌、库存低于阈值就在控制台输出告警提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;简易数据分析：统计一段时间内最高价、最低价、价格波动幅度，输出简报。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;完整可运行代码&lt;/h2&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22%23%20-*-%20coding%3A%20utf-8%20-*-%5Cnimport%20requests%5Cnimport%20json%5Cnimport%20time%5Cnimport%20schedule%5Cnimport%20pandas%20as%20pd%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E3%80%90%E8%AF%B7%E4%BF%AE%E6%94%B9%E8%BF%99%E9%87%8C%E7%9A%84%E9%85%8D%E7%BD%AE%E5%8F%82%E6%95%B0%E3%80%91%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5CnACCESS_KEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E8%AE%BF%E9%97%AEkey%5C%22%5CnACCESS_SECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%AE%89%E5%85%A8%E5%AF%86%E9%92%A5%5C%22%5Cn%5Cn%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%94%BE%E5%A4%9A%E4%B8%AA%5CnMONITOR_GOODS%20%3D%20%5B%5Cn%20%20%20%20%5C%2210335871600%5C%22%2C%5Cn%20%20%20%20%5C%2210057467958584%5C%22%5Cn%5D%5Cn%5Cn%23%20%E9%A2%84%E8%AD%A6%E9%98%88%E5%80%BC%E8%AE%BE%E7%BD%AE%5CnPRICE_DROP_ALERT%20%3D%200.10%20%20%20%23%20%E4%BB%B7%E6%A0%BC%E4%B8%8B%E8%B7%8C%E8%B6%85%E8%BF%8710%25%E6%8F%90%E9%86%92%5CnSTOCK_WARN_NUM%20%3D%2050%20%20%20%20%20%20%20%23%20%E5%BA%93%E5%AD%98%E4%BD%8E%E4%BA%8E50%E8%A7%A6%E5%8F%91%E5%BA%93%E5%AD%98%E5%91%8A%E8%AD%A6%5CnCHECK_PERIOD%20%3D%2060%20*%2060%20%20%20%20%23%20%E5%B7%A1%E6%A3%80%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E8%BF%99%E9%87%8C1%E5%B0%8F%E6%97%B6%E8%B7%91%E4%B8%80%E6%AC%A1%5CnEXPORT_FILE%20%3D%20%5C%22%E5%95%86%E5%93%81%E7%9B%91%E6%8E%A7%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95.xlsx%5C%22%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cndef%20fetch_goods_detail(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%8E%B7%E5%8F%96%E5%8D%95%E4%B8%AA%E5%95%86%E5%93%81%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5C%22%5C%22%5C%22%5Cn%20%20%20%20base_url%20%3D%20%5C%22https%3A%2F%2Fapi-gw.onebound.cn%2Fjd%2Fitem_get_pro%2F%5C%22%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20ACCESS_KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20ACCESS_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%5C%22cache%5C%22%3A%20%5C%22no%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22Accept-Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(base_url%2C%20params%3Dparams%2C%20headers%3Dheaders%2C%20timeout%3D20)%5Cn%20%20%20%20%20%20%20%20res_json%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20error_code%20%3D%20res_json.get(%5C%22error_code%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20error_code%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E5%BC%82%E5%B8%B8%E3%80%91%E5%95%86%E5%93%81%7Bgoods_id%7D%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%EF%BC%8C%E5%8E%9F%E5%9B%A0%EF%BC%9A%7Bres_json.get(&amp;#39;reason&amp;#39;%2C&amp;#39;%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF&amp;#39;)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20item_data%20%3D%20res_json.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20item_data%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E5%BC%82%E5%B8%B8%20goods_id%3A%7Bgoods_id%7D%20%2Cerr%3A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20parse_item(raw_item)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%8A%8A%E5%8E%9F%E5%A7%8B%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%8F%90%E5%8F%96%E6%88%91%E4%BB%AC%E9%9C%80%E8%A6%81%E7%9A%84%E6%A0%B8%E5%BF%83%E5%AD%97%E6%AE%B5%5C%22%5C%22%5C%22%5Cn%20%20%20%20if%20not%20raw_item%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20data%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22crawl_time%5C%22%3A%20datetime.now().strftime(%5C%22%25Y-%25m-%25d%20%25H%3A%25M%3A%25S%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20raw_item.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20raw_item.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20raw_item.get(%5C%22brand%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20raw_item.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%3A%20float(raw_item.get(%5C%22price%5C%22%2C%200)%20or%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20float(raw_item.get(%5C%22orginal_price%5C%22%2C0)%20or%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20int(raw_item.get(%5C%22num%5C%22%2C0)%20or%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22sales%5C%22%3A%20int(raw_item.get(%5C%22sales%5C%22%2C0)%20or%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22detail_url%5C%22%3A%20raw_item.get(%5C%22detail_url%5C%22%2C%5C%22%5C%22)%2C%5Cn%20%20%20%20%7D%5Cn%20%20%20%20return%20data%5Cn%5Cn%5Cndef%20save_to_excel(data_list)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%BF%BD%E5%8A%A0%E5%86%99%E5%85%A5Excel%E4%BF%9D%E5%AD%98%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%5C%22%5C%22%5C%22%5Cn%20%20%20%20new_df%20%3D%20pd.DataFrame(data_list)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20old_df%20%3D%20pd.read_excel(EXPORT_FILE)%5Cn%20%20%20%20%20%20%20%20merge_df%20%3D%20pd.concat(%5Bold_df%2C%20new_df%5D%2C%20ignore_index%3DTrue)%5Cn%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20merge_df%20%3D%20new_df%5Cn%20%20%20%20merge_df.to_excel(EXPORT_FILE%2C%20index%3DFalse)%5Cn%5Cn%5Cndef%20check_alert(new_data%2C%20history_df)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%BC%82%E5%8A%A8%E5%91%8A%E8%AD%A6%E5%88%A4%E6%96%AD%EF%BC%9A%E4%BB%B7%E6%A0%BC%E3%80%81%E5%BA%93%E5%AD%98%5C%22%5C%22%5C%22%5Cn%20%20%20%20goods_id%20%3D%20new_data%5B%5C%22goods_id%5C%22%5D%5Cn%20%20%20%20same_goods%20%3D%20history_df%5Bhistory_df%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20goods_id%5D%5Cn%20%20%20%20if%20len(same_goods)%20%3C%3D%200%3A%5Cn%20%20%20%20%20%20%20%20return%20%5B%5D%5Cn%20%20%20%20last_row%20%3D%20same_goods.iloc%5B-1%5D%5Cn%20%20%20%20alerts%20%3D%20%5B%5D%5Cn%20%20%20%20%23%20%E4%BB%B7%E6%A0%BC%E4%B8%8B%E8%B7%8C%E6%A3%80%E6%B5%8B%5Cn%20%20%20%20old_price%20%3D%20last_row%5B%5C%22current_price%5C%22%5D%5Cn%20%20%20%20new_price%20%3D%20new_data%5B%5C%22current_price%5C%22%5D%5Cn%20%20%20%20if%20old_price%20%3E%200%20and%20new_price%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20drop_rate%20%3D%20(old_price%20-%20new_price)%20%2F%20old_price%5Cn%20%20%20%20%20%20%20%20if%20drop_rate%20%3E%3D%20PRICE_DROP_ALERT%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20alerts.append(f%5C%22%E2%9A%A0%E9%99%8D%E4%BB%B7%E9%A2%84%E8%AD%A6%EF%BC%81%E5%95%86%E5%93%81%E3%80%90%7Bnew_data%5B&amp;#39;title&amp;#39;%5D%7D%E3%80%91%20%E7%94%B1%7Bold_price%7D%20%E2%86%92%20%7Bnew_price%7D%EF%BC%8C%E8%B7%8C%E5%B9%85%7Bround(drop_rate*100%2C2)%7D%25%5C%22)%5Cn%20%20%20%20%23%20%E5%BA%93%E5%AD%98%E9%A2%84%E8%AD%A6%5Cn%20%20%20%20if%20new_data%5B%5C%22stock%5C%22%5D%20%3C%3D%20STOCK_WARN_NUM%3A%5Cn%20%20%20%20%20%20%20%20alerts.append(f%5C%22%E2%9A%A0%E5%BA%93%E5%AD%98%E9%A2%84%E8%AD%A6%EF%BC%81%E5%95%86%E5%93%81%E3%80%90%7Bnew_data%5B&amp;#39;title&amp;#39;%5D%7D%E3%80%91%20%E5%BD%93%E5%89%8D%E5%BA%93%E5%AD%98%E4%BB%85%E5%89%A9%20%7Bnew_data%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20return%20alerts%5Cn%5Cn%5Cndef%20simple_analysis(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E7%AE%80%E5%8D%95%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%EF%BC%9A%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0%E5%8E%86%E5%8F%B2%EF%BC%8C%E8%BE%93%E5%87%BA%E6%9C%80%E9%AB%98%E4%BB%B7%E3%80%81%E6%9C%80%E4%BD%8E%E4%BB%B7%E3%80%81%E5%9D%87%E5%80%BC%5C%22%5C%22%5C%22%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df%20%3D%20pd.read_excel(EXPORT_FILE)%5Cn%20%20%20%20%20%20%20%20target%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20goods_id%5D%5Cn%20%20%20%20%20%20%20%20if%20len(target)%20%3D%3D%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20info%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20target.iloc%5B-1%5D%5B%5C%22title%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22max_price%5C%22%3A%20round(target%5B%5C%22current_price%5C%22%5D.max()%2C2)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22min_price%5C%22%3A%20round(target%5B%5C%22current_price%5C%22%5D.min()%2C2)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22avg_price%5C%22%3A%20round(target%5B%5C%22current_price%5C%22%5D.mean()%2C2)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22record_count%5C%22%3A%20len(target)%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20return%20info%5Cn%20%20%20%20except%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20monitor_task()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%8D%95%E6%AC%A1%E5%B7%A1%E6%A3%80%E4%B8%BB%E4%BB%BB%E5%8A%A1%5C%22%5C%22%5C%22%5Cn%20%20%20%20print(%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E5%BC%80%E5%A7%8B%E4%B8%80%E8%BD%AE%E5%B7%A1%E6%A3%80%20%7B%7D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%22.format(datetime.now()))%5Cn%20%20%20%20collect_result%20%3D%20%5B%5D%5Cn%20%20%20%20for%20gid%20in%20MONITOR_GOODS%3A%5Cn%20%20%20%20%20%20%20%20raw%20%3D%20fetch_goods_detail(gid)%5Cn%20%20%20%20%20%20%20%20item%20%3D%20parse_item(raw)%5Cn%20%20%20%20%20%20%20%20if%20not%20item%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20collect_result.append(item)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E9%87%87%E9%9B%86%E6%88%90%E5%8A%9F%EF%BD%9C%7Bitem%5B&amp;#39;goods_id&amp;#39;%5D%7D%EF%BD%9C%7Bitem%5B&amp;#39;title&amp;#39;%5D%5B%3A25%5D%7D...%EF%BD%9C%E7%8E%B0%E4%BB%B7%EF%BC%9A%7Bitem%5B&amp;#39;current_price&amp;#39;%5D%7D%EF%BD%9C%E5%BA%93%E5%AD%98%EF%BC%9A%7Bitem%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%5Cn%20%20%20%20%23%20%E4%BF%9D%E5%AD%98%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20if%20len(collect_result)%20%3E0%3A%5Cn%20%20%20%20%20%20%20%20save_to_excel(collect_result)%5Cn%5Cn%20%20%20%20%23%20%E8%AF%BB%E5%8F%96%E5%85%A8%E9%83%A8%E5%8E%86%E5%8F%B2%E7%94%A8%E4%BA%8E%E5%91%8A%E8%AD%A6%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20history_all%20%3D%20pd.read_excel(EXPORT_FILE)%5Cn%20%20%20%20except%3A%5Cn%20%20%20%20%20%20%20%20history_all%20%3D%20pd.DataFrame()%5Cn%5Cn%20%20%20%20%23%20%E8%BE%93%E5%87%BA%E5%91%8A%E8%AD%A6%E4%BF%A1%E6%81%AF%5Cn%20%20%20%20for%20row%20in%20collect_result%3A%5Cn%20%20%20%20%20%20%20%20alert_list%20%3D%20check_alert(row%2C%20history_all)%5Cn%20%20%20%20%20%20%20%20for%20msg%20in%20alert_list%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(msg)%5Cn%5Cn%20%20%20%20%23%20%E8%BE%93%E5%87%BA%E7%BB%9F%E8%AE%A1%E7%AE%80%E6%8A%A5%5Cn%20%20%20%20print(%5C%22%5C%5Cn----------%E6%9C%AC%E8%BD%AE%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E7%AE%80%E6%8A%A5----------%5C%22)%5Cn%20%20%20%20for%20gid%20in%20MONITOR_GOODS%3A%5Cn%20%20%20%20%20%20%20%20ana%20%3D%20simple_analysis(gid)%5Cn%20%20%20%20%20%20%20%20if%20ana%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%3A%7Bana%5B&amp;#39;title&amp;#39;%5D%5B%3A22%5D%7D%20%20%E6%9C%80%E9%AB%98%7Bana%5B&amp;#39;max_price&amp;#39;%5D%7D%20%E6%9C%80%E4%BD%8E%7Bana%5B&amp;#39;min_price&amp;#39;%5D%7D%20%E5%9D%87%E4%BB%B7%7Bana%5B&amp;#39;avg_price&amp;#39;%5D%7D%20%E8%AE%B0%E5%BD%95%E6%95%B0%3A%7Bana%5B&amp;#39;record_count&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20print(%5C%22%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E6%9C%AC%E8%BD%AE%E5%B7%A1%E6%A3%80%E7%BB%93%E6%9D%9F%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%5Cn%5C%22)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20print(%5C%22%E7%9B%91%E6%8E%A7%E7%A8%8B%E5%BA%8F%E5%B7%B2%E5%90%AF%E5%8A%A8%EF%BC%8C%E6%8C%89%20Ctrl%2BC%20%E5%81%9C%E6%AD%A2%E7%A8%8B%E5%BA%8F%5C%22)%5Cn%20%20%20%20monitor_task()%20%23%20%E5%85%88%E7%AB%8B%E5%88%BB%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%5Cn%20%20%20%20schedule.every(CHECK_PERIOD).seconds.do(monitor_task)%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20schedule.run_pending()%5Cn%20%20%20%20%20%20%20%20time.sleep(1)%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:&amp;nbsp;utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;json
import&amp;nbsp;time
import&amp;nbsp;schedule
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

#&amp;nbsp;======================【请修改这里的配置参数】======================
ACCESS_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的访问key&amp;quot;
ACCESS_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的安全密钥&amp;quot;

#&amp;nbsp;需要监控的商品编号列表，可以放多个
MONITOR_GOODS&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;10335871600&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;10057467958584&amp;quot;
]

#&amp;nbsp;预警阈值设置
PRICE_DROP_ALERT&amp;nbsp;=&amp;nbsp;0.10&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;价格下跌超过10%提醒
STOCK_WARN_NUM&amp;nbsp;=&amp;nbsp;50&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;库存低于50触发库存告警
CHECK_PERIOD&amp;nbsp;=&amp;nbsp;60&amp;nbsp;*&amp;nbsp;60&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;巡检间隔，单位秒，这里1小时跑一次
EXPORT_FILE&amp;nbsp;=&amp;nbsp;&amp;quot;商品监控历史记录.xlsx&amp;quot;
#&amp;nbsp;=================================================================

def&amp;nbsp;fetch_goods_detail(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;获取单个商品结构化数据&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api-gw.onebound.cn/jd/item_get_pro/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;ACCESS_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;ACCESS_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;cache&amp;quot;:&amp;nbsp;&amp;quot;no&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept-Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(base_url,&amp;nbsp;params=params,&amp;nbsp;headers=headers,&amp;nbsp;timeout=20)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res_json&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_code&amp;nbsp;=&amp;nbsp;res_json.get(&amp;quot;error_code&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;error_code&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【异常】商品{goods_id}获取失败，原因：{res_json.get(&amp;#39;reason&amp;#39;,&amp;#39;未知错误&amp;#39;)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;res_json.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;item_data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;网络请求异常&amp;nbsp;goods_id:{goods_id}&amp;nbsp;,err:{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;parse_item(raw_item):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;把原始返回数据，提取我们需要的核心字段&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;raw_item:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;crawl_time&amp;quot;:&amp;nbsp;datetime.now().strftime(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;brand&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;:&amp;nbsp;float(raw_item.get(&amp;quot;price&amp;quot;,&amp;nbsp;0)&amp;nbsp;or&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;float(raw_item.get(&amp;quot;orginal_price&amp;quot;,0)&amp;nbsp;or&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;int(raw_item.get(&amp;quot;num&amp;quot;,0)&amp;nbsp;or&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sales&amp;quot;:&amp;nbsp;int(raw_item.get(&amp;quot;sales&amp;quot;,0)&amp;nbsp;or&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;detail_url&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;detail_url&amp;quot;,&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;data


def&amp;nbsp;save_to_excel(data_list):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;追加写入Excel保存历史记录&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new_df&amp;nbsp;=&amp;nbsp;pd.DataFrame(data_list)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;old_df&amp;nbsp;=&amp;nbsp;pd.read_excel(EXPORT_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;merge_df&amp;nbsp;=&amp;nbsp;pd.concat([old_df,&amp;nbsp;new_df],&amp;nbsp;ignore_index=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;merge_df&amp;nbsp;=&amp;nbsp;new_df
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;merge_df.to_excel(EXPORT_FILE,&amp;nbsp;index=False)


def&amp;nbsp;check_alert(new_data,&amp;nbsp;history_df):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;异动告警判断：价格、库存&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id&amp;nbsp;=&amp;nbsp;new_data[&amp;quot;goods_id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;same_goods&amp;nbsp;=&amp;nbsp;history_df[history_df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;goods_id]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(same_goods)&amp;nbsp;&amp;lt;=&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_row&amp;nbsp;=&amp;nbsp;same_goods.iloc[-1]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alerts&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;价格下跌检测
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;old_price&amp;nbsp;=&amp;nbsp;last_row[&amp;quot;current_price&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new_price&amp;nbsp;=&amp;nbsp;new_data[&amp;quot;current_price&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;old_price&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;and&amp;nbsp;new_price&amp;nbsp;&amp;gt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;drop_rate&amp;nbsp;=&amp;nbsp;(old_price&amp;nbsp;-&amp;nbsp;new_price)&amp;nbsp;/&amp;nbsp;old_price
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;drop_rate&amp;nbsp;&amp;gt;=&amp;nbsp;PRICE_DROP_ALERT:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alerts.append(f&amp;quot;⚠降价预警！商品【{new_data[&amp;#39;title&amp;#39;]}】&amp;nbsp;由{old_price}&amp;nbsp;→&amp;nbsp;{new_price}，跌幅{round(drop_rate*100,2)}%&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;库存预警
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;new_data[&amp;quot;stock&amp;quot;]&amp;nbsp;&amp;lt;=&amp;nbsp;STOCK_WARN_NUM:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alerts.append(f&amp;quot;⚠库存预警！商品【{new_data[&amp;#39;title&amp;#39;]}】&amp;nbsp;当前库存仅剩&amp;nbsp;{new_data[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;alerts


def&amp;nbsp;simple_analysis(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;简单数据分析：读取本地历史，输出最高价、最低价、均值&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_excel(EXPORT_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;target&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;goods_id]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(target)&amp;nbsp;==&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;info&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;target.iloc[-1][&amp;quot;title&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;max_price&amp;quot;:&amp;nbsp;round(target[&amp;quot;current_price&amp;quot;].max(),2),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;min_price&amp;quot;:&amp;nbsp;round(target[&amp;quot;current_price&amp;quot;].min(),2),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;avg_price&amp;quot;:&amp;nbsp;round(target[&amp;quot;current_price&amp;quot;].mean(),2),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;record_count&amp;quot;:&amp;nbsp;len(target)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;info
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;monitor_task():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;单次巡检主任务&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n==========开始一轮巡检&amp;nbsp;{}==========&amp;quot;.format(datetime.now()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;collect_result&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;MONITOR_GOODS:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raw&amp;nbsp;=&amp;nbsp;fetch_goods_detail(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item&amp;nbsp;=&amp;nbsp;parse_item(raw)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;collect_result.append(item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;采集成功｜{item[&amp;#39;goods_id&amp;#39;]}｜{item[&amp;#39;title&amp;#39;][:25]}...｜现价：{item[&amp;#39;current_price&amp;#39;]}｜库存：{item[&amp;#39;stock&amp;#39;]}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;保存数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(collect_result)&amp;nbsp;&amp;gt;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_excel(collect_result)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;读取全部历史用于告警
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;history_all&amp;nbsp;=&amp;nbsp;pd.read_excel(EXPORT_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;history_all&amp;nbsp;=&amp;nbsp;pd.DataFrame()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;输出告警信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;row&amp;nbsp;in&amp;nbsp;collect_result:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert_list&amp;nbsp;=&amp;nbsp;check_alert(row,&amp;nbsp;history_all)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;msg&amp;nbsp;in&amp;nbsp;alert_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(msg)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;输出统计简报
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n----------本轮数据分析简报----------&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;MONITOR_GOODS:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ana&amp;nbsp;=&amp;nbsp;simple_analysis(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;ana:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品:{ana[&amp;#39;title&amp;#39;][:22]}&amp;nbsp;&amp;nbsp;最高{ana[&amp;#39;max_price&amp;#39;]}&amp;nbsp;最低{ana[&amp;#39;min_price&amp;#39;]}&amp;nbsp;均价{ana[&amp;#39;avg_price&amp;#39;]}&amp;nbsp;记录数:{ana[&amp;#39;record_count&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;==========本轮巡检结束==========\n&amp;quot;)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;监控程序已启动，按&amp;nbsp;Ctrl+C&amp;nbsp;停止程序&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor_task()&amp;nbsp;#&amp;nbsp;先立刻执行一次
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.every(CHECK_PERIOD).seconds.do(monitor_task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.run_pending()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(1)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;使用步骤说明&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将代码中&lt;code&gt;ACCESS_KEY&lt;/code&gt;、&lt;code&gt;ACCESS_SECRET&lt;/code&gt;替换为自己的凭证，修改&lt;code&gt;MONITOR_GOODS&lt;/code&gt;填入需要监控的商品编号。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;按需修改预警阈值：降价提醒比例、库存告警数量、巡检时间间隔。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;直接运行脚本：&lt;code&gt;python goods_monitor.py&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;运行之后程序会：	&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;立刻执行一次数据拉取；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;按照设定间隔自动循环巡检；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;本地生成&lt;code&gt;商品监控历史记录.xlsx&lt;/code&gt;，保存全部历史采集数据；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;控制台打印日志，出现降价、库存不足时输出警告；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;每轮结束输出简单统计简报，看到该商品历史最高、最低、平均价格。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;h2&gt;可以拓展的方向&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;消息通知&lt;/strong&gt;：把告警信息接入企业微信、钉钉机器人，不用盯着控制台，异动直接推送消息。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多规格 SKU 深度监控&lt;/strong&gt;：代码示例只取了商品主价格，可以进一步解析 skus 数组，对每一个子规格分别做价格、库存跟踪。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可视化图表&lt;/strong&gt;：利用 matplotlib 或者 pyecharts 读取 Excel 历史数据，绘制价格趋势曲线图。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;存储升级&lt;/strong&gt;：数据量大之后，可以把存储从 Excel 替换成 SQLite 或者 MySQL，更适合长期大量数据。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;批量商品导入&lt;/strong&gt;：从 csv 读取一大批商品 ID，实现上百个商品批量监控。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;踩坑小提示&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;不要把巡检间隔设置过小，过于频繁会触发访问限制，建议最少间隔 30 分钟以上。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;部分商品会返回价格不准，这是平台数据特性，做监控时需要留意，不要完全依赖单一数值做决策。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;遇到商品下架，脚本会打印异常日志，方便你及时清理监控列表。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;服务器部署可以使用 nohup 或者 systemd 后台常驻，实现 7×24 小时不间断监控。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;这套工具把我们从重复复制粘贴网页的工作中解放出来，把精力更多放在数据分析和业务决策上，而不是机械的数据收集。你可以基于这份基础代码，根据自己业务需求不断迭代改造。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Tue, 20 Oct 2026 14:26:03 +0800</pubDate></item><item><title>告别人工盯品！Open‑Claw 快速搭建电商商品监控与数据分析完整方案（附可运行 Python 代码）</title><link>https://www.shopapi.com.cn/?id=240</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品盯价、货源监控、价格走势分析、库存异动跟踪，本地电脑、云服务器都可以直接部署运行。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营、选品、竞品分析的朋友，大多会遇到一个痛点：想要持续跟踪一批商品的售价变动、库存变化、规格调整，手动点开网页反复查看效率极低。尤其需要同时盯十几、几十款商品，每天反复截图、复制记录，耗费大量时间，还很容易漏掉调价、清库存这类关键信号。&lt;/p&gt;&lt;p&gt;自己手写页面解析方案，又会遇到不少麻烦：页面结构改版导致字段提取失效、访问频率过高被限制访问、动态页面渲染拿不到完整规格信息、SKU 多规格数据很难完整拆解。&lt;/p&gt;&lt;p&gt;最近在实践中发现 Open‑Claw 这套工具可以很好解决上述问题。只需要输入商品编号，就能拿到完整结构化的商品信息，包含标题、原价、实时售价、多规格 SKU、库存、品牌、店铺信息、图集等内容，内置缓存、异常处理机制，不用处理复杂的页面解析逻辑，能把开发工作量大幅降低。&lt;/p&gt;&lt;p&gt;本文分享一套完整可落地的监控方案，包含数据拉取、时序存储、异动告警、数据分析可视化完整流程，代码可以直接复制修改配置就运行。&lt;/p&gt;&lt;h2&gt;整体方案设计&lt;/h2&gt;&lt;p&gt;整套系统拆分为 4 个模块，模块之间低耦合，后续可以按需扩展功能：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E6%95%B0%E6%8D%AE%E8%8E%B7%E5%8F%96%E6%A8%A1%E5%9D%97%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22ud7Dfb-1792462719241%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;数据获取模块&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;数据获取模块&quot;&gt;数据获取模块&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：传入商品编号，拿到完整结构化商品信息，处理网络异常、商品不存在等各类异常情况。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据持久化模块&lt;/strong&gt;：每次采集带上时间戳，保存历史记录，这里选用 CSV 文件，不需要额外安装数据库，普通办公电脑直接打开表格查看历史数据。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异动监控告警模块&lt;/strong&gt;：对比本次采集和上一轮历史数据，识别价格跳水、库存骤降，达到设定阈值就输出告警提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析模块&lt;/strong&gt;：基于留存的时序历史数据，完成价格走势绘图、多规格对比、竞品统计分析，输出可视化图表。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;对比传统页面解析方式，这套方案优势：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;稳定性更好，不用担心页面样式改版导致解析失效；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;一次性返回全部规格、店铺、图片信息，不需要多次请求拼接；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;自带缓存策略，重复查询可以加速返回，减少网络消耗；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;代码量少，新手也能快速看懂、二次修改。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;环境准备&lt;/h2&gt;&lt;p&gt;需要安装依赖库，命令行执行：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22pip%20install%20requests%20pandas%20matplotlib%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;matplotlib&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;完整代码实现&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cnimport%20requests%5Cnimport%20json%5Cnimport%20time%5Cnimport%20pandas%20as%20pd%5Cnfrom%20datetime%20import%20datetime%5Cnimport%20matplotlib.pyplot%20as%20plt%5Cn%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E3%80%90%E8%AF%B7%E8%87%AA%E8%A1%8C%E4%BF%AE%E6%94%B9%E9%85%8D%E7%BD%AE%E5%8C%BA%E5%9F%9F%E3%80%91%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5CnCLAW_KEY%20%3D%20%5C%22%E5%A1%AB%E5%85%A5%E4%BD%A0%E7%9A%84key%5C%22%5CnCLAW_SECRET%20%3D%20%5C%22%E5%A1%AB%E5%85%A5%E4%BD%A0%E7%9A%84secret%5C%22%5Cn%5Cn%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E5%88%97%E8%A1%A8%5CnMONITOR_GOODS%20%3D%20%5B%5C%2210335871600%5C%22%2C%20%5C%2210057467958584%5C%22%5D%5Cn%23%20%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E5%BB%BA%E8%AE%AE%E4%B8%8D%E8%A6%81%E8%AE%BE%E7%BD%AE%E8%BF%87%E5%B0%8F%EF%BC%8C300%E5%8D%B35%E5%88%86%E9%92%9F%5CnPOLL_SECONDS%20%3D%20300%5Cn%23%20%E9%99%8D%E4%BB%B7%E5%91%8A%E8%AD%A6%E9%98%88%E5%80%BC%EF%BC%8C0.1%E4%BB%A3%E8%A1%A8%E7%9B%B8%E6%AF%94%E4%B8%8A%E4%B8%80%E6%AC%A1%EF%BC%8C%E4%BB%B7%E6%A0%BC%E4%B8%8B%E8%B7%8C%E8%B6%85%E8%BF%8710%25%E8%A7%A6%E5%8F%91%E6%8F%90%E9%86%92%5CnPRICE_ALERT_RATIO%20%3D%200.1%5Cn%23%20%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E6%96%87%E4%BB%B6%5CnSAVE_CSV%20%3D%20%5C%22goods_monitor_history.csv%5C%22%5Cn%23%20%E6%98%AF%E5%90%A6%E5%BC%80%E5%90%AF%E7%BC%93%E5%AD%98%EF%BC%8C%E5%BC%80%E5%90%AF%E5%90%8E%E4%BC%98%E5%85%88%E8%AF%BB%E5%8F%96%E7%BC%93%E5%AD%98%EF%BC%8C%E9%80%9F%E5%BA%A6%E6%9B%B4%E5%BF%AB%5CnUSE_CACHE%20%3D%20%5C%22yes%5C%22%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cndef%20fetch_goods_detail(goods_id%3A%20str)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E8%8E%B7%E5%8F%96%E5%8D%95%E6%AC%BE%E5%95%86%E5%93%81%E5%AE%8C%E6%95%B4%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20%3Aparam%20goods_id%3A%20%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20%3Areturn%3A%20dict%20%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%A4%B1%E8%B4%A5%E8%BF%94%E5%9B%9ENone%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20base_url%20%3D%20%5C%22https%3A%2F%2Fapi-gw.onebound.cn%2Fjd%2Fitem_get_pro%5C%22%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20CLAW_KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20CLAW_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%5C%22cache%5C%22%3A%20USE_CACHE%2C%5Cn%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(base_url%2C%20params%3Dparams%2C%20timeout%3D15%2C%20headers%3D%7B%5C%22Accept%E2%80%91Encoding%5C%22%3A%20%5C%22gzip%5C%22%7D)%5Cn%20%20%20%20%20%20%20%20resp.raise_for_status()%5Cn%20%20%20%20%20%20%20%20res_json%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20%23%20%E5%BC%82%E5%B8%B8%E5%88%A4%E6%96%AD%5Cn%20%20%20%20%20%20%20%20if%20res_json.get(%5C%22error_code%5C%22)%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20err_msg%20%3D%20res_json.get(%5C%22reason%5C%22%2C%20res_json.get(%5C%22error%5C%22%2C%20%5C%22%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF%5C%22))%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E9%87%87%E9%9B%86%E5%BC%82%E5%B8%B8%E3%80%91%E5%95%86%E5%93%81%7Bgoods_id%7D%EF%BC%9A%7Berr_msg%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20item_data%20%3D%20res_json.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20not%20item_data%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E9%87%87%E9%9B%86%E5%BC%82%E5%B8%B8%E3%80%91%E5%95%86%E5%93%81%7Bgoods_id%7D%E8%BF%94%E5%9B%9E%E5%86%85%E5%AE%B9%E4%B8%BA%E7%A9%BA%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20return%20item_data%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%E3%80%91%E5%95%86%E5%93%81%7Bgoods_id%7D%EF%BC%8C%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20save_history(data_list)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%BF%BD%E5%8A%A0%E5%86%99%E5%85%A5%E5%8E%86%E5%8F%B2%E6%97%B6%E5%BA%8F%E6%95%B0%E6%8D%AE%E5%88%B0csv%E6%96%87%E4%BB%B6%5C%22%5C%22%5C%22%5Cn%20%20%20%20df_new%20%3D%20pd.DataFrame(data_list)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df_old%20%3D%20pd.read_csv(SAVE_CSV)%5Cn%20%20%20%20%20%20%20%20df_merge%20%3D%20pd.concat(%5Bdf_old%2C%20df_new%5D%2C%20ignore_index%3DTrue)%5Cn%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20df_merge%20%3D%20df_new%5Cn%20%20%20%20df_merge.to_csv(SAVE_CSV%2C%20index%3DFalse%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22)%5Cn%5Cn%5Cndef%20check_alert(goods_id%2C%20current_price%2C%20last_price)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E4%BB%B7%E6%A0%BC%E5%BC%82%E5%8A%A8%E5%91%8A%E8%AD%A6%E9%80%BB%E8%BE%91%5C%22%5C%22%5C%22%5Cn%20%20%20%20if%20last_price%20is%20None%20or%20current_price%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20return%20False%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20curr%20%3D%20float(current_price)%5Cn%20%20%20%20%20%20%20%20last%20%3D%20float(last_price)%5Cn%20%20%20%20%20%20%20%20if%20last%20%3C%3D%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20False%5Cn%20%20%20%20%20%20%20%20drop_ratio%20%3D%20(last%20-%20curr)%20%2F%20last%5Cn%20%20%20%20%20%20%20%20if%20drop_ratio%20%3E%3D%20PRICE_ALERT_RATIO%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22%20*%2060)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%20%E5%91%8A%E8%AD%A6%EF%BC%81%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%E5%8F%91%E7%94%9F%E6%98%8E%E6%98%BE%E9%99%8D%E4%BB%B7%EF%BC%81%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E4%B8%8A%E4%B8%80%E8%BD%AE%E4%BB%B7%E6%A0%BC%EF%BC%9A%7Blast%7D%EF%BC%8C%E5%BD%93%E5%89%8D%E4%BB%B7%E6%A0%BC%EF%BC%9A%7Bcurr%7D%EF%BC%8C%E9%99%8D%E5%B9%85%20%7Bround(drop_ratio*100%2C2)%7D%25%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22%20*%2060)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20True%5Cn%20%20%20%20except%3A%5Cn%20%20%20%20%20%20%20%20pass%5Cn%20%20%20%20return%20False%5Cn%5Cn%5Cndef%20analysis_draw(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%EF%BC%8C%E7%BB%98%E5%88%B6%E4%BB%B7%E6%A0%BC%E8%B5%B0%E5%8A%BF%E5%9B%BE%E8%A1%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(SAVE_CSV%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22)%5Cn%20%20%20%20%20%20%20%20df_target%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20goods_id%5D.copy()%5Cn%20%20%20%20%20%20%20%20if%20len(df_target)%20%3C%202%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%7Bgoods_id%7D%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E4%B8%8D%E8%B6%B3%EF%BC%8C%E6%97%A0%E6%B3%95%E7%BB%98%E5%9B%BE%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%5Cn%20%20%20%20%20%20%20%20df_target%5B%5C%22crawl_time%5C%22%5D%20%3D%20pd.to_datetime(df_target%5B%5C%22crawl_time%5C%22%5D)%5Cn%20%20%20%20%20%20%20%20plt.rcParams%5B%5C%22font.sans%E2%80%91serif%5C%22%5D%20%3D%20%5B%5C%22SimHei%5C%22%5D%5Cn%20%20%20%20%20%20%20%20plt.figure(figsize%3D(10%2C%205))%5Cn%20%20%20%20%20%20%20%20plt.plot(df_target%5B%5C%22crawl_time%5C%22%5D%2C%20df_target%5B%5C%22price%5C%22%5D%2C%20marker%3D%5C%22o%5C%22%2C%20label%3D%5C%22%E5%AE%9E%E6%97%B6%E5%94%AE%E4%BB%B7%5C%22)%5Cn%20%20%20%20%20%20%20%20plt.title(f%5C%22%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%E4%BB%B7%E6%A0%BC%E8%B5%B0%E5%8A%BF%5C%22)%5Cn%20%20%20%20%20%20%20%20plt.xlabel(%5C%22%E6%97%B6%E9%97%B4%5C%22)%5Cn%20%20%20%20%20%20%20%20plt.ylabel(%5C%22%E4%BB%B7%E6%A0%BC%5C%22)%5Cn%20%20%20%20%20%20%20%20plt.legend()%5Cn%20%20%20%20%20%20%20%20plt.grid(alpha%3D0.3)%5Cn%20%20%20%20%20%20%20%20plt.tight_layout()%5Cn%20%20%20%20%20%20%20%20plt.savefig(f%5C%22price_trend_%7Bgoods_id%7D.png%5C%22%2C%20dpi%3D150)%5Cn%20%20%20%20%20%20%20%20plt.close()%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E2%9C%85%20%E5%95%86%E5%93%81%7Bgoods_id%7D%E4%BB%B7%E6%A0%BC%E8%B5%B0%E5%8A%BF%E5%9B%BE%E5%B7%B2%E4%BF%9D%E5%AD%98%E5%88%B0%E6%9C%AC%E5%9C%B0%5C%22)%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E7%BB%98%E5%9B%BE%E5%A4%B1%E8%B4%A5%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%5Cn%5Cndef%20start_monitor()%3A%5Cn%20%20%20%20print(%5C%22%3D%3D%3D%3D%3D%E5%95%86%E5%93%81%E7%9B%91%E6%8E%A7%E7%A8%8B%E5%BA%8F%E5%B7%B2%E5%90%AF%E5%8A%A8%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20last_record%20%3D%20dict()%20%20%23%20%E4%BF%9D%E5%AD%98%E4%B8%8A%E4%B8%80%E8%BD%AE%E9%87%87%E9%9B%86%E7%BB%93%E6%9E%9C%EF%BC%8C%E7%94%A8%E4%BA%8E%E6%AF%94%E5%AF%B9%E5%91%8A%E8%AD%A6%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20batch_save%20%3D%20%5B%5D%5Cn%20%20%20%20%20%20%20%20for%20gid%20in%20MONITOR_GOODS%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item_info%20%3D%20fetch_goods_detail(gid)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20item_info%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20%20%20%20%20now_time%20%3D%20datetime.now().strftime(%5C%22%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title%20%3D%20item_info.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20price%20%3D%20item_info.get(%5C%22price%5C%22%2C%200)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20origin_price%20%3D%20item_info.get(%5C%22orginal_price%5C%22%2C%200)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20stock%20%3D%20item_info.get(%5C%22num%5C%22%2C%200)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20shop_name%20%3D%20item_info.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E6%8F%90%E5%8F%96%E7%AC%AC%E4%B8%80%E6%9D%A1%E8%A7%84%E6%A0%BC%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%AE%8C%E6%95%B4SKU%E5%88%97%E8%A1%A8%E5%AD%98%E5%9C%A8%20item_info%5B%5C%22skus%5C%22%5D%E4%B8%AD%5Cn%20%20%20%20%20%20%20%20%20%20%20%20one_row%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22crawl_time%5C%22%3A%20now_time%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20gid%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20title%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22price%5C%22%3A%20price%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20origin_price%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20stock%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20shop_name%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20batch_save.append(one_row)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%91%8A%E8%AD%A6%E5%88%A4%E6%96%AD%5Cn%20%20%20%20%20%20%20%20%20%20%20%20last_p%20%3D%20last_record.get(gid%2C%20%7B%7D).get(%5C%22price%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20check_alert(gid%2C%20price%2C%20last_p)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20last_record%5Bgid%5D%20%3D%20one_row%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%5B%7Bnow_time%7D%5D%20%E5%B7%B2%E9%87%87%E9%9B%86%EF%BC%9A%7Bgid%7D%20%7C%20%7Btitle%5B%3A25%5D%7D%20%EF%BD%9C%E7%8E%B0%E4%BB%B7%3A%7Bprice%7D%20%E5%BA%93%E5%AD%98%3A%7Bstock%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%23%20%E6%89%B9%E9%87%8F%E5%86%99%E5%85%A5%E6%96%87%E4%BB%B6%5Cn%20%20%20%20%20%20%20%20if%20len(batch_save)%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20save_history(batch_save)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E6%9C%AC%E8%BD%AE%E9%87%87%E9%9B%86%E5%AE%8C%E6%88%90%EF%BC%8C%E4%BC%91%E7%9C%A0%20%7BPOLL_SECONDS%7D%20%E7%A7%92%E7%AD%89%E5%BE%85%E4%B8%8B%E4%B8%80%E8%BD%AE...%5C%5Cn%5C%22)%5Cn%20%20%20%20%20%20%20%20time.sleep(POLL_SECONDS)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20%23%20%E5%90%AF%E5%8A%A8%E6%8C%81%E7%BB%AD%E7%9B%91%E6%8E%A7%5Cn%20%20%20%20start_monitor()%5Cn%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E5%8F%AA%E6%83%B3%E5%81%9A%E4%B8%80%E6%AC%A1%E6%80%A7%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%EF%BC%8C%E6%B3%A8%E9%87%8A%E4%B8%8A%E9%9D%A2%EF%BC%8C%E8%B0%83%E7%94%A8%E4%B8%8B%E9%9D%A2%E5%87%BD%E6%95%B0%E4%BC%A0%E5%85%A5%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20%23%20analysis_draw(%5C%2210335871600%5C%22)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;json
import&amp;nbsp;time
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime
import&amp;nbsp;matplotlib.pyplot&amp;nbsp;as&amp;nbsp;plt

#&amp;nbsp;======================【请自行修改配置区域】======================
CLAW_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;填入你的key&amp;quot;
CLAW_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;填入你的secret&amp;quot;

#&amp;nbsp;需要监控的商品编号列表
MONITOR_GOODS&amp;nbsp;=&amp;nbsp;[&amp;quot;10335871600&amp;quot;,&amp;nbsp;&amp;quot;10057467958584&amp;quot;]
#&amp;nbsp;循环监控间隔，单位秒，建议不要设置过小，300即5分钟
POLL_SECONDS&amp;nbsp;=&amp;nbsp;300
#&amp;nbsp;降价告警阈值，0.1代表相比上一次，价格下跌超过10%触发提醒
PRICE_ALERT_RATIO&amp;nbsp;=&amp;nbsp;0.1
#&amp;nbsp;历史数据存储文件
SAVE_CSV&amp;nbsp;=&amp;nbsp;&amp;quot;goods_monitor_history.csv&amp;quot;
#&amp;nbsp;是否开启缓存，开启后优先读取缓存，速度更快
USE_CACHE&amp;nbsp;=&amp;nbsp;&amp;quot;yes&amp;quot;
#&amp;nbsp;=================================================================

def&amp;nbsp;fetch_goods_detail(goods_id:&amp;nbsp;str):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获取单款商品完整结构化数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:param&amp;nbsp;goods_id:&amp;nbsp;商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:return:&amp;nbsp;dict&amp;nbsp;商品信息，失败返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api-gw.onebound.cn/jd/item_get_pro&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;CLAW_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;CLAW_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;cache&amp;quot;:&amp;nbsp;USE_CACHE,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(base_url,&amp;nbsp;params=params,&amp;nbsp;timeout=15,&amp;nbsp;headers={&amp;quot;Accept‑Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res_json&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;异常判断
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;res_json.get(&amp;quot;error_code&amp;quot;)&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;res_json.get(&amp;quot;reason&amp;quot;,&amp;nbsp;res_json.get(&amp;quot;error&amp;quot;,&amp;nbsp;&amp;quot;未知错误&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【采集异常】商品{goods_id}：{err_msg}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;res_json.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【采集异常】商品{goods_id}返回内容为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;item_data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【网络异常】商品{goods_id}，错误信息：{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;save_history(data_list):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;追加写入历史时序数据到csv文件&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new&amp;nbsp;=&amp;nbsp;pd.DataFrame(data_list)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_old&amp;nbsp;=&amp;nbsp;pd.read_csv(SAVE_CSV)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_merge&amp;nbsp;=&amp;nbsp;pd.concat([df_old,&amp;nbsp;df_new],&amp;nbsp;ignore_index=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_merge&amp;nbsp;=&amp;nbsp;df_new
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_merge.to_csv(SAVE_CSV,&amp;nbsp;index=False,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;)


def&amp;nbsp;check_alert(goods_id,&amp;nbsp;current_price,&amp;nbsp;last_price):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;价格异动告警逻辑&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;last_price&amp;nbsp;is&amp;nbsp;None&amp;nbsp;or&amp;nbsp;current_price&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curr&amp;nbsp;=&amp;nbsp;float(current_price)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last&amp;nbsp;=&amp;nbsp;float(last_price)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;last&amp;nbsp;&amp;lt;=&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;drop_ratio&amp;nbsp;=&amp;nbsp;(last&amp;nbsp;-&amp;nbsp;curr)&amp;nbsp;/&amp;nbsp;last
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;drop_ratio&amp;nbsp;&amp;gt;=&amp;nbsp;PRICE_ALERT_RATIO:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;&amp;nbsp;*&amp;nbsp;60)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️&amp;nbsp;告警！商品&amp;nbsp;{goods_id}&amp;nbsp;发生明显降价！&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;上一轮价格：{last}，当前价格：{curr}，降幅&amp;nbsp;{round(drop_ratio*100,2)}%&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;&amp;nbsp;*&amp;nbsp;60)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;True
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pass
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;False


def&amp;nbsp;analysis_draw(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;读取本地历史数据，绘制价格走势图表&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(SAVE_CSV,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_target&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;goods_id].copy()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(df_target)&amp;nbsp;&amp;lt;&amp;nbsp;2:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品{goods_id}历史记录不足，无法绘图&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_target[&amp;quot;crawl_time&amp;quot;]&amp;nbsp;=&amp;nbsp;pd.to_datetime(df_target[&amp;quot;crawl_time&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.rcParams[&amp;quot;font.sans‑serif&amp;quot;]&amp;nbsp;=&amp;nbsp;[&amp;quot;SimHei&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.figure(figsize=(10,&amp;nbsp;5))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.plot(df_target[&amp;quot;crawl_time&amp;quot;],&amp;nbsp;df_target[&amp;quot;price&amp;quot;],&amp;nbsp;marker=&amp;quot;o&amp;quot;,&amp;nbsp;label=&amp;quot;实时售价&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.title(f&amp;quot;商品&amp;nbsp;{goods_id}&amp;nbsp;价格走势&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.xlabel(&amp;quot;时间&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.ylabel(&amp;quot;价格&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.legend()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.grid(alpha=0.3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.tight_layout()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.savefig(f&amp;quot;price_trend_{goods_id}.png&amp;quot;,&amp;nbsp;dpi=150)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;✅&amp;nbsp;商品{goods_id}价格走势图已保存到本地&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;绘图失败：{str(e)}&amp;quot;)


def&amp;nbsp;start_monitor():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=====商品监控程序已启动=====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_record&amp;nbsp;=&amp;nbsp;dict()&amp;nbsp;&amp;nbsp;#&amp;nbsp;保存上一轮采集结果，用于比对告警
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_save&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;MONITOR_GOODS:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_info&amp;nbsp;=&amp;nbsp;fetch_goods_detail(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;now_time&amp;nbsp;=&amp;nbsp;datetime.now().strftime(&amp;quot;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;price&amp;quot;,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;origin_price&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;num&amp;quot;,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shop_name&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;简单提取第一条规格信息，完整SKU列表存在&amp;nbsp;item_info[&amp;quot;skus&amp;quot;]中
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;one_row&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;crawl_time&amp;quot;:&amp;nbsp;now_time,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;gid,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;title,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;:&amp;nbsp;price,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;origin_price,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;stock,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;shop_name
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;batch_save.append(one_row)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;告警判断
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_p&amp;nbsp;=&amp;nbsp;last_record.get(gid,&amp;nbsp;{}).get(&amp;quot;price&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;check_alert(gid,&amp;nbsp;price,&amp;nbsp;last_p)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_record[gid]&amp;nbsp;=&amp;nbsp;one_row
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;[{now_time}]&amp;nbsp;已采集：{gid}&amp;nbsp;|&amp;nbsp;{title[:25]}&amp;nbsp;｜现价:{price}&amp;nbsp;库存:{stock}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;批量写入文件
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(batch_save)&amp;nbsp;&amp;gt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_history(batch_save)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;本轮采集完成，休眠&amp;nbsp;{POLL_SECONDS}&amp;nbsp;秒等待下一轮...\n&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(POLL_SECONDS)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;启动持续监控
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start_monitor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;如果只想做一次性数据分析，注释上面，调用下面函数传入商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;analysis_draw(&amp;quot;10335871600&amp;quot;)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;代码说明与扩展方向&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;配置修改&lt;/strong&gt;：只需要修改代码最上方配置区，填入自己的密钥、填写需要监控的商品编号，设置循环间隔就可以直接运行。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;注意：轮询间隔不要设置过小，过于频繁会触发访问限制。&lt;/p&gt;&lt;/blockquote&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;SKU 多规格处理&lt;/strong&gt;：返回结果中&lt;code&gt;skus&lt;/code&gt;字段包含全部规格，每一条规格带有规格名称、规格编号、对应价格、库存。可以在代码中循环解析&lt;code&gt;item_info[&amp;quot;skus&amp;quot;][&amp;quot;sku&amp;quot;]&lt;/code&gt;，把每一个子规格单独存一行记录，实现多规格分别监控。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警能力拓展&lt;/strong&gt;：目前告警仅控制台打印，实际项目可以拓展，触发告警后调用企业微信 / 钉钉机器人、发送邮件，实现消息推送。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析拓展&lt;/strong&gt;：	&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;读取本地 CSV，可以做多款竞品横向价格对比；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;统计原价、现价差值，计算折扣力度；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;筛选库存低于阈值的商品做库存预警；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;按店铺做批量统计，输出店铺商品大盘报表。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常状态处理&lt;/strong&gt;：返回结果会包含各类异常标记，比如商品下架、编号错误、访问受限，代码中已经做基础捕获，可以根据业务需求增加对应处理逻辑，比如商品下架打印提示。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;踩坑经验总结&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;价格字段偶尔会出现数据延迟，不能完全单一依赖单次采集的价格做业务决策，建议结合历史多轮数据综合判断。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;开启缓存模式可以加快返回速度，如果需要拿到实时最新状态，可以将&lt;code&gt;USE_CACHE&lt;/code&gt;设置为&lt;code&gt;no&lt;/code&gt;，但会消耗更多额度。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;尽量不要设置很短的轮询周期，合理设置间隔，避免短时间大量请求。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;存储 CSV 文件用&lt;code&gt;utf‑8‑sig&lt;/code&gt;编码，Windows 系统 Excel 打开不会出现中文乱码。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;小结&lt;/h2&gt;&lt;p&gt;对于做竞品跟踪、货源调研的同学，这套方案可以把原本大量人工重复操作交给程序自动完成。不需要深入研究页面逆向解析，把精力放在业务数据分析上。程序持续后台运行，自动留存时序数据，价格变动第一时间收到提醒，还可以直接生成价格走势图，辅助选品、调价决策。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Tue, 20 Oct 2026 10:29:12 +0800</pubDate></item><item><title>告别手工盯盘：用 Open‑Claw 搭建电商商品自动化监控与数据分析实战</title><link>https://www.shopapi.com.cn/?id=239</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品跟踪、价格波动监控、库存变动记录、历史行情统计；Python 完整可运行示例，适合个人开发者、电商运营、数据分析同学参考。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营或者竞品调研的时候，我们经常需要持续关注一批商品：价格有没有下调、库存是否清零、不同规格套餐的售价变化、店铺基础信息更新。如果依靠人工反复打开网页复制记录，不仅耗费大量时间，还很容易错过关键变动，历史数据也很难留存用于复盘分析。&lt;/p&gt;&lt;p&gt;很多同学会尝试直接解析网页页面，但平台页面结构经常改版，CSS 选择器隔一段时间就失效，调试成本非常高。Open‑Claw 这套工具集合，帮我们屏蔽了页面解析的繁琐工作，直接输出结构化的商品原始资料，我们只需要专注业务逻辑：定时拉取、存储历史记录、做对比分析，实现一套轻量化的商品监控系统。&lt;/p&gt;&lt;p&gt;本文会完整讲解整个实现思路，给出可直接调试的 Python 代码，实现：商品基础信息获取、定时循环监控、数据落盘保存、价格波动识别，简单的数据分析统计。&lt;/p&gt;&lt;h2&gt;整体实现思路&lt;/h2&gt;&lt;p&gt;整套程序分为 4 个核心环节：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E8%B5%84%E6%96%99%E6%8B%89%E5%8F%96%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22rZKbgV-1791943407440%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;资料拉取&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;资料拉取&quot;&gt;资料拉取&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：输入商品编号，拿到标题、现价、原价、库存、多规格套餐、店铺名称、主图等完整结构化内容；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;持久化存储&lt;/strong&gt;：把每一次采集的结果保存，形成时间序列历史记录；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;变动检测&lt;/strong&gt;：对比上一轮记录，识别价格涨跌、库存变动；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;简单分析&lt;/strong&gt;：读取历史文件，计算最高价、最低价、价格变动频次，输出统计结果。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;注意：使用前需要提前准备好你的访问凭证，包含两组密钥信息。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;环境准备&lt;/h2&gt;&lt;p&gt;依赖安装，执行下面命令：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22pip%20install%20requests%20pandas%20schedule%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;schedule&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;requests：负责网络请求收发&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;pandas：用来做数据整理、存储 csv、简单统计分析&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;schedule：实现定时循环任务，周期性巡检商品状态&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;完整代码实现&lt;/h2&gt;&lt;p&gt;新建&lt;code&gt;goods_monitor.py&lt;/code&gt;，复制下面代码，修改配置区域的凭证、待监控商品编号、监控周期即可运行。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cnimport%20requests%5Cnimport%20json%5Cnimport%20time%5Cnimport%20pandas%20as%20pd%5Cnimport%20schedule%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E3%80%90%E9%85%8D%E7%BD%AE%E5%8C%BA%EF%BC%8C%E8%AF%B7%E4%BF%AE%E6%94%B9%E8%BF%99%E9%87%8C%E3%80%91%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%23%20%E4%BD%A0%E7%9A%84%E4%B8%A4%E7%BB%84%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81%5CnACCESS_KEY%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E4%B8%BA%E4%BD%A0%E7%9A%84key%5C%22%5CnACCESS_SECRET%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E4%B8%BA%E4%BD%A0%E7%9A%84secret%5C%22%5Cn%5Cn%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%A1%AB%E5%86%99%E5%A4%9A%E4%B8%AA%5CnMONITOR_GOODS_ID_LIST%20%3D%20%5B%5C%2210335871600%5C%22%5D%5Cn%5Cn%23%20%E7%9B%91%E6%8E%A7%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E7%A4%BA%E4%BE%8B1800%3D30%E5%88%86%E9%92%9F%E8%B7%91%E4%B8%80%E6%AC%A1%5CnCHECK_INTERVAL%20%3D%201800%5Cn%5Cn%23%20%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E4%BF%9D%E5%AD%98%E6%96%87%E4%BB%B6%5CnHISTORY_CSV%20%3D%20%5C%22.%2Fgoods_history.csv%5C%22%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cndef%20get_goods_raw_data(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E5%8E%9F%E5%A7%8B%E7%BB%93%E6%9E%84%E5%8C%96%E8%B5%84%E6%96%99%5Cn%20%20%20%20goods_id%3A%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20%E8%BF%94%E5%9B%9E%E8%A7%A3%E6%9E%90%E5%90%8E%E7%9A%84%E5%AD%97%E5%85%B8%EF%BC%8C%E5%A4%B1%E8%B4%A5%E8%BF%94%E5%9B%9ENone%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20url%3D%5C%22https%3A%2F%2Fapi%E2%80%91gw.onebound.cn%2Fjd%2Fitem_get_pro%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20params%3D%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20ACCESS_KEY%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20ACCESS_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%7D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20headers%3D%7B%5C%22Accept%E2%80%91Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%7D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20timeout%3D25%5Cn%20%20%20%20%20%20%20%20)%5Cn%20%20%20%20%20%20%20%20result%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%BF%94%E5%9B%9E%E7%8A%B6%E6%80%81%5Cn%20%20%20%20%20%20%20%20if%20result.get(%5C%22error_code%5C%22)%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20err_msg%20%3D%20result.get(%5C%22reason%5C%22%2C%20%5C%22%E6%9C%AA%E7%9F%A5%E5%BC%82%E5%B8%B8%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%E3%80%91%7Berr_msg%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20item_data%20%3D%20result.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20not%20item_data%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%E8%BF%94%E5%9B%9E%E5%86%85%E5%AE%B9%E4%B8%BA%E7%A9%BA%E3%80%91%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20return%20item_data%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E7%BD%91%E7%BB%9C%E6%88%96%E8%A7%A3%E6%9E%90%E5%BC%82%E5%B8%B8%20goods_id%3D%7Bgoods_id%7D%2C%20error%3A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20parse_item_info(raw_item)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E4%BB%8E%E5%8E%9F%E5%A7%8B%E8%B5%84%E6%96%99%E6%8F%90%E5%8F%96%E6%88%91%E4%BB%AC%E5%85%B3%E5%BF%83%E7%9A%84%E4%B8%9A%E5%8A%A1%E5%AD%97%E6%AE%B5%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20sku_list%20%3D%20%5B%5D%5Cn%20%20%20%20sku_raw%20%3D%20raw_item.get(%5C%22skus%5C%22%2C%20%7B%7D).get(%5C%22sku%5C%22%2C%20%5B%5D)%5Cn%20%20%20%20for%20sku%20in%20sku_raw%3A%5Cn%20%20%20%20%20%20%20%20sku_list.append(%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_id%5C%22%3A%20sku.get(%5C%22sku_id%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_name%5C%22%3A%20sku.get(%5C%22properties_name%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22price%5C%22%3A%20float(sku.get(%5C%22price%5C%22%2C%200))%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20float(sku.get(%5C%22orginal_price%5C%22%2C%200))%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20int(sku.get(%5C%22quantity%5C%22%2C%200))%5Cn%20%20%20%20%20%20%20%20%7D)%5Cn%5Cn%20%20%20%20output%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22capture_time%5C%22%3A%20datetime.now().strftime(%5C%22%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20raw_item.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20raw_item.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20raw_item.get(%5C%22brand%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20raw_item.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%3A%20float(raw_item.get(%5C%22price%5C%22%2C%200))%2C%5Cn%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20float(raw_item.get(%5C%22orginal_price%5C%22%2C%200))%2C%5Cn%20%20%20%20%20%20%20%20%5C%22stock_total%5C%22%3A%20raw_item.get(%5C%22num%5C%22%2C%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22detail_url%5C%22%3A%20raw_item.get(%5C%22detail_url%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22main_image%5C%22%3A%20raw_item.get(%5C%22pic_url%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22skus%5C%22%3A%20json.dumps(sku_list%2C%20ensure_ascii%3DFalse)%5Cn%20%20%20%20%7D%5Cn%20%20%20%20return%20output%5Cn%5Cn%5Cndef%20save_to_csv(row_data)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%BF%BD%E5%8A%A0%E5%86%99%E5%85%A5%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95csv%5C%22%5C%22%5C%22%5Cn%20%20%20%20df_new%20%3D%20pd.DataFrame(%5Brow_data%5D)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df_old%20%3D%20pd.read_csv(HISTORY_CSV)%5Cn%20%20%20%20%20%20%20%20df_all%20%3D%20pd.concat(%5Bdf_old%2C%20df_new%5D%2C%20ignore_index%3DTrue)%5Cn%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20df_all%20%3D%20df_new%5Cn%20%20%20%20df_all.to_csv(HISTORY_CSV%2C%20index%3DFalse%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22)%5Cn%5Cn%5Cndef%20detect_change(last_record%2C%20curr_record)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E7%AE%80%E5%8D%95%E5%8F%98%E5%8A%A8%E5%AF%B9%E6%AF%94%EF%BC%8C%E8%AF%86%E5%88%AB%E4%BB%B7%E6%A0%BC%E3%80%81%E5%BA%93%E5%AD%98%E5%8F%98%E5%8C%96%5C%22%5C%22%5C%22%5Cn%20%20%20%20msg_list%20%3D%20%5B%5D%5Cn%20%20%20%20if%20last_record%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20msg_list.append(%5C%22%E9%A6%96%E6%AC%A1%E9%87%87%E9%9B%86%EF%BC%8C%E6%97%A0%E5%8E%86%E5%8F%B2%E5%AF%B9%E6%AF%94%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20msg_list%5Cn%20%20%20%20last_price%20%3D%20float(last_record.get(%5C%22current_price%5C%22%2C%200))%5Cn%20%20%20%20curr_price%20%3D%20curr_record.get(%5C%22current_price%5C%22)%5Cn%20%20%20%20last_stock%20%3D%20int(last_record.get(%5C%22stock_total%5C%22%2C%200))%5Cn%20%20%20%20curr_stock%20%3D%20curr_record.get(%5C%22stock_total%5C%22)%5Cn%5Cn%20%20%20%20if%20curr_price%20!%3D%20last_price%3A%5Cn%20%20%20%20%20%20%20%20diff%20%3D%20curr_price%20-%20last_price%5Cn%20%20%20%20%20%20%20%20if%20diff%20%3C%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20msg_list.append(f%5C%22%E2%9A%A0%EF%B8%8F%E4%BB%B7%E6%A0%BC%E4%B8%8B%E9%99%8D%EF%BC%9A%7Blast_price%7D%20%E2%86%92%20%7Bcurr_price%7D%EF%BC%8C%E9%99%8D%E5%B9%85%7Babs(diff)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20msg_list.append(f%5C%22%E2%9A%A0%EF%B8%8F%E4%BB%B7%E6%A0%BC%E4%B8%8A%E6%B6%A8%EF%BC%9A%7Blast_price%7D%20%E2%86%92%20%7Bcurr_price%7D%EF%BC%8C%E6%B6%A8%E5%B9%85%7Bdiff%7D%5C%22)%5Cn%20%20%20%20if%20curr_stock%20!%3D%20last_stock%3A%5Cn%20%20%20%20%20%20%20%20msg_list.append(f%5C%22%E2%9A%A0%EF%B8%8F%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%EF%BC%9A%7Blast_stock%7D%20%E2%86%92%20%7Bcurr_stock%7D%5C%22)%5Cn%20%20%20%20if%20len(msg_list)%20%3D%3D%200%3A%5Cn%20%20%20%20%20%20%20%20msg_list.append(%5C%22%E2%9C%85%E6%9C%AC%E6%AC%A1%E9%87%87%E9%9B%86%E6%97%A0%E6%98%8E%E6%98%BE%E5%8F%98%E5%8A%A8%5C%22)%5Cn%20%20%20%20return%20msg_list%5Cn%5Cn%5Cndef%20monitor_task()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%8D%95%E6%AC%A1%E7%9B%91%E6%8E%A7%E4%BB%BB%E5%8A%A1%EF%BC%8C%E5%BE%AA%E7%8E%AF%E5%A4%84%E7%90%86%E6%89%80%E6%9C%89%E5%BE%85%E7%9B%91%E6%8E%A7%E5%95%86%E5%93%81%5C%22%5C%22%5C%22%5Cn%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%3D%E5%BC%80%E5%A7%8B%E5%B7%A1%E6%A3%80%20%7Bdatetime.now().strftime(&amp;#39;%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S&amp;#39;)%7D%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20for%20goods_id%20in%20MONITOR_GOODS_ID_LIST%3A%5Cn%20%20%20%20%20%20%20%20raw%20%3D%20get_goods_raw_data(goods_id)%5Cn%20%20%20%20%20%20%20%20if%20raw%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20curr_info%20%3D%20parse_item_info(raw)%5Cn%20%20%20%20%20%20%20%20%23%20%E8%AF%BB%E5%8F%96%E4%B8%8A%E4%B8%80%E6%9D%A1%E5%90%8C%E5%95%86%E5%93%81%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E7%94%A8%E4%BA%8E%E5%AF%B9%E6%AF%94%5Cn%20%20%20%20%20%20%20%20last_one%20%3D%20None%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df_history%20%3D%20pd.read_csv(HISTORY_CSV)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20goods_history%20%3D%20df_history%5Bdf_history%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20str(goods_id)%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20not%20goods_history.empty%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20last_one%20%3D%20goods_history.iloc%5B-1%5D.to_dict()%5Cn%20%20%20%20%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20pass%5Cn%5Cn%20%20%20%20%20%20%20%20change_msgs%20%3D%20detect_change(last_one%2C%20curr_info)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%E5%95%86%E5%93%81%E3%80%90%7Bcurr_info%5B&amp;#39;title&amp;#39;%5D%7D%E3%80%91%5C%22)%5Cn%20%20%20%20%20%20%20%20for%20m%20in%20change_msgs%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(m)%5Cn%20%20%20%20%20%20%20%20save_to_csv(curr_info)%5Cn%20%20%20%20print(%5C%22%3D%3D%3D%3D%3D%3D%3D%3D%3D%E6%9C%AC%E8%BD%AE%E5%B7%A1%E6%A3%80%E7%BB%93%E6%9D%9F%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%5Cn%5C%22)%5Cn%5Cn%5Cndef%20analysis_history()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0%E5%8E%86%E5%8F%B2%E6%96%87%E4%BB%B6%E5%81%9A%E7%AE%80%E5%8D%95%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%EF%BC%8C%E8%BE%93%E5%87%BA%E7%BB%9F%E8%AE%A1%E7%BB%93%E6%9E%9C%5C%22%5C%22%5C%22%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(HISTORY_CSV)%5Cn%20%20%20%20%20%20%20%20if%20df.empty%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%9A%82%E6%97%A0%E5%8E%86%E5%8F%B2%E5%88%86%E6%9E%90%E6%95%B0%E6%8D%AE%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%3D%3D%3D%3D%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20for%20gid%20in%20df%5B%5C%22goods_id%5C%22%5D.unique()%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sub_df%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20gid%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title%20%3D%20sub_df.iloc%5B0%5D%5B%5C%22title%5C%22%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20max_p%20%3D%20sub_df%5B%5C%22current_price%5C%22%5D.max()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20min_p%20%3D%20sub_df%5B%5C%22current_price%5C%22%5D.min()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20avg_p%20%3D%20round(sub_df%5B%5C%22current_price%5C%22%5D.mean()%2C%202)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20count_row%20%3D%20len(sub_df)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%EF%BC%9A%7Btitle%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%20%20%E9%87%87%E6%A0%B7%E6%AC%A1%E6%95%B0%EF%BC%9A%7Bcount_row%7D%20%E6%AC%A1%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%20%20%E5%8E%86%E5%8F%B2%E6%9C%80%E9%AB%98%EF%BC%9A%7Bmax_p%7D%EF%BC%8C%E5%8E%86%E5%8F%B2%E6%9C%80%E4%BD%8E%EF%BC%9A%7Bmin_p%7D%EF%BC%8C%E5%B9%B3%E5%9D%87%E4%BB%B7%EF%BC%9A%7Bavg_p%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22-%5C%22*40)%5Cn%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E8%BF%98%E6%B2%A1%E6%9C%89%E7%94%9F%E6%88%90%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E6%96%87%E4%BB%B6%EF%BC%8C%E8%AF%B7%E5%85%88%E8%BF%90%E8%A1%8C%E7%9B%91%E6%8E%A7%E9%87%87%E9%9B%86%E3%80%82%5C%22)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20%23%20%E7%AB%8B%E5%8D%B3%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%5Cn%20%20%20%20monitor_task()%5Cn%20%20%20%20%23%20%E8%AE%BE%E7%BD%AE%E5%AE%9A%E6%97%B6%E8%B0%83%E5%BA%A6%5Cn%20%20%20%20schedule.every(CHECK_INTERVAL).seconds.do(monitor_task)%5Cn%20%20%20%20print(f%5C%22%E5%B7%B2%E5%90%AF%E5%8A%A8%E5%AE%9A%E6%97%B6%E7%9B%91%E6%8E%A7%EF%BC%8C%E6%AF%8F%20%7BCHECK_INTERVAL%7D%20%E7%A7%92%E6%89%A7%E8%A1%8C%E4%B8%80%E8%BD%AE%EF%BC%8C%E6%8C%89Ctrl%2BC%E7%BB%88%E6%AD%A2%E7%A8%8B%E5%BA%8F%5C%22)%5Cn%20%20%20%20print(%5C%22%E8%BE%93%E5%85%A5%20a%20%E5%9B%9E%E8%BD%A6%E5%8F%AF%E4%BB%A5%E6%89%A7%E8%A1%8C%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%5C%22)%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20schedule.run_pending()%5Cn%20%20%20%20%20%20%20%20time.sleep(2)%5Cn%20%20%20%20%20%20%20%20%23%20%E7%AE%80%E6%98%93%E4%BA%A4%E4%BA%92%EF%BC%8C%E8%BE%93%E5%85%A5a%E6%89%A7%E8%A1%8C%E5%88%86%E6%9E%90%E6%8A%A5%E5%91%8A%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20import%20sys%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20sys.stdin%20in%20select.select(%5Bsys.stdin%5D%2C%20%5B%5D%2C%20%5B%5D%2C%200)%5B0%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20line%20%3D%20sys.stdin.readline()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20if%20line.strip()%20%3D%3D%20%5C%22a%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20analysis_history()%5Cn%20%20%20%20%20%20%20%20except%20Exception%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20pass%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;json
import&amp;nbsp;time
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
import&amp;nbsp;schedule
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

#&amp;nbsp;======================【配置区，请修改这里】======================
#&amp;nbsp;你的两组访问凭证
ACCESS_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;替换为你的key&amp;quot;
ACCESS_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;替换为你的secret&amp;quot;

#&amp;nbsp;需要监控的商品编号，可以填写多个
MONITOR_GOODS_ID_LIST&amp;nbsp;=&amp;nbsp;[&amp;quot;10335871600&amp;quot;]

#&amp;nbsp;监控间隔，单位秒，示例1800=30分钟跑一次
CHECK_INTERVAL&amp;nbsp;=&amp;nbsp;1800

#&amp;nbsp;历史数据保存文件
HISTORY_CSV&amp;nbsp;=&amp;nbsp;&amp;quot;./goods_history.csv&amp;quot;
#&amp;nbsp;=================================================================

def&amp;nbsp;get_goods_raw_data(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获取商品原始结构化资料
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id:商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;返回解析后的字典，失败返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;url=&amp;quot;https://api‑gw.onebound.cn/jd/item_get_pro&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params={
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;ACCESS_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;ACCESS_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers={&amp;quot;Accept‑Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout=25
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;判断返回状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;result.get(&amp;quot;error_code&amp;quot;)&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;result.get(&amp;quot;reason&amp;quot;,&amp;nbsp;&amp;quot;未知异常&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【商品{goods_id}获取失败】{err_msg}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;result.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【商品{goods_id}返回内容为空】&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;item_data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;网络或解析异常&amp;nbsp;goods_id={goods_id},&amp;nbsp;error:{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;parse_item_info(raw_item):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;从原始资料提取我们关心的业务字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_raw&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;skus&amp;quot;,&amp;nbsp;{}).get(&amp;quot;sku&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;sku_raw:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list.append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_id&amp;quot;:&amp;nbsp;sku.get(&amp;quot;sku_id&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_name&amp;quot;:&amp;nbsp;sku.get(&amp;quot;properties_name&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;:&amp;nbsp;float(sku.get(&amp;quot;price&amp;quot;,&amp;nbsp;0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;float(sku.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;int(sku.get(&amp;quot;quantity&amp;quot;,&amp;nbsp;0))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;output&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;capture_time&amp;quot;:&amp;nbsp;datetime.now().strftime(&amp;quot;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;brand&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;:&amp;nbsp;float(raw_item.get(&amp;quot;price&amp;quot;,&amp;nbsp;0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;float(raw_item.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock_total&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;num&amp;quot;,&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;detail_url&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;detail_url&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;main_image&amp;quot;:&amp;nbsp;raw_item.get(&amp;quot;pic_url&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;skus&amp;quot;:&amp;nbsp;json.dumps(sku_list,&amp;nbsp;ensure_ascii=False)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;output


def&amp;nbsp;save_to_csv(row_data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;追加写入历史记录csv&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new&amp;nbsp;=&amp;nbsp;pd.DataFrame([row_data])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_old&amp;nbsp;=&amp;nbsp;pd.read_csv(HISTORY_CSV)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all&amp;nbsp;=&amp;nbsp;pd.concat([df_old,&amp;nbsp;df_new],&amp;nbsp;ignore_index=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all&amp;nbsp;=&amp;nbsp;df_new
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all.to_csv(HISTORY_CSV,&amp;nbsp;index=False,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;)


def&amp;nbsp;detect_change(last_record,&amp;nbsp;curr_record):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;简单变动对比，识别价格、库存变化&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;last_record&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list.append(&amp;quot;首次采集，无历史对比&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;msg_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_price&amp;nbsp;=&amp;nbsp;float(last_record.get(&amp;quot;current_price&amp;quot;,&amp;nbsp;0))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curr_price&amp;nbsp;=&amp;nbsp;curr_record.get(&amp;quot;current_price&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_stock&amp;nbsp;=&amp;nbsp;int(last_record.get(&amp;quot;stock_total&amp;quot;,&amp;nbsp;0))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curr_stock&amp;nbsp;=&amp;nbsp;curr_record.get(&amp;quot;stock_total&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;curr_price&amp;nbsp;!=&amp;nbsp;last_price:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;diff&amp;nbsp;=&amp;nbsp;curr_price&amp;nbsp;-&amp;nbsp;last_price
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;diff&amp;nbsp;&amp;lt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list.append(f&amp;quot;⚠️价格下降：{last_price}&amp;nbsp;→&amp;nbsp;{curr_price}，降幅{abs(diff)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list.append(f&amp;quot;⚠️价格上涨：{last_price}&amp;nbsp;→&amp;nbsp;{curr_price}，涨幅{diff}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;curr_stock&amp;nbsp;!=&amp;nbsp;last_stock:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list.append(f&amp;quot;⚠️库存变动：{last_stock}&amp;nbsp;→&amp;nbsp;{curr_stock}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(msg_list)&amp;nbsp;==&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;msg_list.append(&amp;quot;✅本次采集无明显变动&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;msg_list


def&amp;nbsp;monitor_task():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;单次监控任务，循环处理所有待监控商品&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n=========开始巡检&amp;nbsp;{datetime.now().strftime(&amp;#39;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;#39;)}&amp;nbsp;=========&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;goods_id&amp;nbsp;in&amp;nbsp;MONITOR_GOODS_ID_LIST:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raw&amp;nbsp;=&amp;nbsp;get_goods_raw_data(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;raw&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;curr_info&amp;nbsp;=&amp;nbsp;parse_item_info(raw)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;读取上一条同商品历史记录用于对比
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_one&amp;nbsp;=&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_history&amp;nbsp;=&amp;nbsp;pd.read_csv(HISTORY_CSV)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_history&amp;nbsp;=&amp;nbsp;df_history[df_history[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;str(goods_id)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;goods_history.empty:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_one&amp;nbsp;=&amp;nbsp;goods_history.iloc[-1].to_dict()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pass

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msgs&amp;nbsp;=&amp;nbsp;detect_change(last_one,&amp;nbsp;curr_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n商品【{curr_info[&amp;#39;title&amp;#39;]}】&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;m&amp;nbsp;in&amp;nbsp;change_msgs:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(m)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_csv(curr_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=========本轮巡检结束=========\n&amp;quot;)


def&amp;nbsp;analysis_history():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;读取本地历史文件做简单数据分析，输出统计结果&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(HISTORY_CSV)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;df.empty:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;暂无历史分析数据&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n====历史数据分析报告====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;df[&amp;quot;goods_id&amp;quot;].unique():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sub_df&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;gid]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;=&amp;nbsp;sub_df.iloc[0][&amp;quot;title&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_p&amp;nbsp;=&amp;nbsp;sub_df[&amp;quot;current_price&amp;quot;].max()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;min_p&amp;nbsp;=&amp;nbsp;sub_df[&amp;quot;current_price&amp;quot;].min()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;avg_p&amp;nbsp;=&amp;nbsp;round(sub_df[&amp;quot;current_price&amp;quot;].mean(),&amp;nbsp;2)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;count_row&amp;nbsp;=&amp;nbsp;len(sub_df)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品：{title}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;&amp;nbsp;&amp;nbsp;采样次数：{count_row}&amp;nbsp;次&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;&amp;nbsp;&amp;nbsp;历史最高：{max_p}，历史最低：{min_p}，平均价：{avg_p}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;-&amp;quot;*40)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;还没有生成历史记录文件，请先运行监控采集。&amp;quot;)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;立即执行一次
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor_task()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;设置定时调度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.every(CHECK_INTERVAL).seconds.do(monitor_task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;已启动定时监控，每&amp;nbsp;{CHECK_INTERVAL}&amp;nbsp;秒执行一轮，按Ctrl+C终止程序&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;输入&amp;nbsp;a&amp;nbsp;回车可以执行历史数据分析报告&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.run_pending()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(2)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;简易交互，输入a执行分析报告
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;import&amp;nbsp;sys
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;sys.stdin&amp;nbsp;in&amp;nbsp;select.select([sys.stdin],&amp;nbsp;[],&amp;nbsp;[],&amp;nbsp;0)[0]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;line&amp;nbsp;=&amp;nbsp;sys.stdin.readline()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;line.strip()&amp;nbsp;==&amp;nbsp;&amp;quot;a&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analysis_history()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;pass&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;补充说明：运行后，程序会按照设定周期自动拉取商品资料，每一轮全部写入&lt;code&gt;goods_history.csv&lt;/code&gt;；控制台输入字母&lt;code&gt;a&lt;/code&gt;回车，即可输出历史统计报告。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;数据字段说明&lt;/h2&gt;&lt;p&gt;保存到 csv 里面会包含这些核心信息：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;capture_time：采集时间戳&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;goods_id：商品编号&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;title：商品完整标题&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;brand：品牌名称&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;shop_name：店铺名称&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;current_price：当前售价&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;origin_price：划线原价&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;stock_total：总库存&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;detail_url：商品网页地址&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;main_image：主图地址&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;skus：JSON 字符串，存放各个规格套餐的编号、名称、价格、库存&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;依靠这份历史表格，我们后续还可以拓展：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;使用 matplotlib 绘制价格走势折线图；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;接入消息推送，发生价格变动的时候发送通知；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;批量导入上百个商品编号做批量竞品巡检；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将 csv 迁移至 sqlite/mysql 数据库，支撑更大体量的数据。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;踩坑记录与注意事项&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;凭证密钥保管&lt;/strong&gt;：两组密钥不要硬编码直接上传到公开代码仓库，正式项目建议放到环境变量或者配置文件。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;采集频次控制&lt;/strong&gt;：不要设置过小的轮询间隔，频繁请求会触发访问限制，建议至少 10 分钟以上一轮。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;规格价格注意&lt;/strong&gt;：部分场景下返回的主条目价格不一定和子规格完全匹配，业务分析优先读取 skus 数组内各个套餐价格。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常容错&lt;/strong&gt;：代码里面已经增加捕获异常，遇到网络波动、商品下架不会直接让整个程序崩溃，控制台会打印错误提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;商品下架情况&lt;/strong&gt;：当商品已经被平台下架，会返回对应提示，脚本会打印日志，不会生成无效记录。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;后续拓展方向&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;增加消息推送：企业微信、钉钉机器人，一旦监控到降价、库存变化直接推送提醒；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;可视化：把历史 csv 数据绘制价格走势图，直观看到一段时间的价格波动；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;持久化数据库：数据量大之后替换 csv，改用 sqlite 数据库存储；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;多平台兼容：这套工具也可以拓展其他平台的商品资料获取，复用监控分析逻辑。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;写在最后&lt;/h2&gt;&lt;p&gt;对于运营、数据分析同学，不需要深度钻研页面逆向、页面反爬对抗，借助 Open‑Claw 直接拿到干净结构化的原始资料，把精力放在业务逻辑：定时巡检、历史留存、变动告警、行情分析，快速搭建一套属于自己的竞品监控工具。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Wed, 14 Oct 2026 10:07:37 +0800</pubDate></item><item><title>OpenClaw 实战｜告别手工盯盘，搭建电商商品自动化监控与数据分析体系</title><link>https://www.shopapi.com.cn/?id=238</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品盯价、库存异动跟踪、促销活动观察、商品数据归档复盘，Python 完整可运行脚本，适合开发者、电商运营做二次落地。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营或者市场调研的时候，很多人都有这样的痛点：需要持续关注一批竞品商品，手动打开网页复制价格、库存、规格信息，一天反复刷好几次，耗费大量时间，还很容易漏掉降价、断货、上新规格这类关键变化。&lt;/p&gt;&lt;p&gt;如果商品数量多，几十上百个商品，纯人工维护基本不现实。直接写原生页面抓取，又会遇到页面加密、动态渲染、频繁访问被限制等一堆麻烦，调试成本很高。&lt;/p&gt;&lt;p&gt;最近在实践 OpenClaw 这套工具链，搭配成熟的数据获取能力，可以快速搭建一套稳定的商品监控系统。不需要处理复杂页面解析逻辑，只需要关注业务逻辑：定时拉取商品信息、对比历史记录、识别异动、存储数据、简单统计分析。&lt;/p&gt;&lt;p&gt;本文就完整分享整套落地流程，从环境准备、单商品数据获取、定时监控、异动检测，再到简单数据分析，全部附带可直接运行的 Python 代码。&lt;/p&gt;&lt;h2&gt;整体实现思路&lt;/h2&gt;&lt;p&gt;整套系统分为 4 个核心环节：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;5&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E6%95%B0%E6%8D%AE%E8%8E%B7%E5%8F%96%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22nMR1GO-1791940167135%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;数据获取&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;数据获取&quot;&gt;数据获取&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：输入商品编号，拿到商品标题、售价、原价、库存、店铺、多规格信息、图片链接等结构化内容。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;本地持久化&lt;/strong&gt;：把每一轮采集的数据保存到本地数据库，留存历史快照，方便后续做对比和数据分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异动检测&lt;/strong&gt;：对比上一轮记录，识别价格变动、库存清零、规格增减等变化，输出告警提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析输出&lt;/strong&gt;：基于历史数据，输出价格波动统计、库存变化记录，导出表格做报表。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;环境依赖：Python3.8 及以上，需要安装 requests、pandas、sqlite3（python 自带）&lt;/p&gt;&lt;/blockquote&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;4&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22pip%20install%20requests%20pandas%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;完整代码实现&lt;/h2&gt;&lt;h3&gt;1. 基础封装：获取商品原始结构化数据&lt;/h3&gt;&lt;p&gt;把基础请求逻辑封装，输入商品编号，返回整理后的商品字典。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cnimport%20requests%5Cnimport%20json%5Cnimport%20time%5Cnimport%20sqlite3%5Cnimport%20pandas%20as%20pd%5Cn%5Cn%23%20--------------------------%20%E9%85%8D%E7%BD%AE%E5%8C%BA%E5%9F%9F%EF%BC%8C%E8%AF%B7%E8%87%AA%E8%A1%8C%E4%BF%AE%E6%94%B9%20--------------------------%5CnACCESS_KEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81key%5C%22%5CnACCESS_SECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81secret%5C%22%5CnMONITOR_GOODS_ID_LIST%20%3D%20%5B%5C%2210335871600%5C%22%2C%20%5C%2210057467958584%5C%22%5D%20%20%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E5%88%97%E8%A1%A8%5CnMONITOR_INTERVAL%20%3D%2060%20*%2030%20%20%23%20%E7%9B%91%E6%8E%A7%E8%BD%AE%E8%AF%A2%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E7%A4%BA%E4%BE%8B30%E5%88%86%E9%92%9F%E8%B7%91%E4%B8%80%E6%AC%A1%5CnDB_FILE%20%3D%20%5C%22.%2Fgoods_monitor.db%5C%22%5Cn%23%20------------------------------------------------------------------------%5Cn%5Cndef%20fetch_goods_detail(goods_id%3A%20str)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E8%AF%A6%E6%83%85%E5%8E%9F%E5%A7%8B%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20%3Aparam%20goods_id%3A%20%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20%3Areturn%3A%20dict%20%E6%95%B4%E7%90%86%E5%90%8E%E7%9A%84%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF%EF%BC%8C%E5%A4%B1%E8%B4%A5%E8%BF%94%E5%9B%9ENone%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20base_url%20%3D%20%5C%22https%3A%2F%2Fapi-gw.onebound.cn%2Fjd%2Fitem_get_pro%2F%5C%22%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20ACCESS_KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20ACCESS_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22Accept-Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(base_url%2C%20params%3Dparams%2C%20headers%3Dheaders%2C%20timeout%3D20)%5Cn%20%20%20%20%20%20%20%20resp_json%20%3D%20resp.json()%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%E3%80%91%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%E8%AF%B7%E6%B1%82%E5%A4%B1%E8%B4%A5%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%BF%94%E5%9B%9E%E7%8A%B6%E6%80%81%5Cn%20%20%20%20if%20resp_json.get(%5C%22error_code%5C%22)%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20err_msg%20%3D%20resp_json.get(%5C%22reason%5C%22%2C%20%5C%22%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%E3%80%91%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%2C%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF%EF%BC%9A%7Berr_msg%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20item_data%20%3D%20resp_json.get(%5C%22item%5C%22%2C%20%7B%7D)%5Cn%20%20%20%20if%20not%20item_data%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E8%AD%A6%E5%91%8A%E3%80%91%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE%E4%B8%BA%E7%A9%BA%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%23%20%E6%95%B4%E7%90%86%E6%A0%B8%E5%BF%83%E5%AD%97%E6%AE%B5%5Cn%20%20%20%20result%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20item_data.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20item_data.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22price%5C%22%3A%20float(item_data.get(%5C%22price%5C%22%2C%200))%20if%20item_data.get(%5C%22price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%5C%22orginal_price%5C%22%3A%20float(item_data.get(%5C%22orginal_price%5C%22%2C%200))%20if%20item_data.get(%5C%22orginal_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20item_data.get(%5C%22num%5C%22%2C%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20item_data.get(%5C%22brand%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20item_data.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22detail_url%5C%22%3A%20item_data.get(%5C%22detail_url%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22capture_ts%5C%22%3A%20int(time.time())%2C%5Cn%20%20%20%20%20%20%20%20%5C%22capture_time%5C%22%3A%20time.strftime(%5C%22%25Y-%25m-%25d%20%25H%3A%25M%3A%25S%5C%22%2C%20time.localtime())%5Cn%20%20%20%20%7D%5Cn%5Cn%20%20%20%20%23%20%E6%8F%90%E5%8F%96%E5%A4%9A%E8%A7%84%E6%A0%BCSKU%E4%BF%A1%E6%81%AF%5Cn%20%20%20%20sku_list%20%3D%20%5B%5D%5Cn%20%20%20%20skus_raw%20%3D%20item_data.get(%5C%22skus%5C%22%2C%20%7B%7D).get(%5C%22sku%5C%22%2C%20%5B%5D)%5Cn%20%20%20%20for%20sku%20in%20skus_raw%3A%5Cn%20%20%20%20%20%20%20%20sku_info%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_id%5C%22%3A%20sku.get(%5C%22sku_id%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_price%5C%22%3A%20float(sku.get(%5C%22price%5C%22%2C0))%20if%20sku.get(%5C%22price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_origin_price%5C%22%3A%20float(sku.get(%5C%22orginal_price%5C%22%2C0))%20if%20sku.get(%5C%22orginal_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_prop%5C%22%3A%20sku.get(%5C%22properties_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_stock%5C%22%3A%20sku.get(%5C%22quantity%5C%22%2C0)%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20sku_list.append(sku_info)%5Cn%20%20%20%20result%5B%5C%22sku_list%5C%22%5D%20%3D%20sku_list%5Cn%20%20%20%20return%20result%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;json
import&amp;nbsp;time
import&amp;nbsp;sqlite3
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd

#&amp;nbsp;--------------------------&amp;nbsp;配置区域，请自行修改&amp;nbsp;--------------------------
ACCESS_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的访问凭证key&amp;quot;
ACCESS_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的访问凭证secret&amp;quot;
MONITOR_GOODS_ID_LIST&amp;nbsp;=&amp;nbsp;[&amp;quot;10335871600&amp;quot;,&amp;nbsp;&amp;quot;10057467958584&amp;quot;]&amp;nbsp;&amp;nbsp;#&amp;nbsp;需要监控的商品编号列表
MONITOR_INTERVAL&amp;nbsp;=&amp;nbsp;60&amp;nbsp;*&amp;nbsp;30&amp;nbsp;&amp;nbsp;#&amp;nbsp;监控轮询间隔，单位秒，示例30分钟跑一次
DB_FILE&amp;nbsp;=&amp;nbsp;&amp;quot;./goods_monitor.db&amp;quot;
#&amp;nbsp;------------------------------------------------------------------------

def&amp;nbsp;fetch_goods_detail(goods_id:&amp;nbsp;str):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获取商品详情原始结构化数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:param&amp;nbsp;goods_id:&amp;nbsp;商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:return:&amp;nbsp;dict&amp;nbsp;整理后的商品信息，失败返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api-gw.onebound.cn/jd/item_get_pro/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;ACCESS_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;ACCESS_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept-Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(base_url,&amp;nbsp;params=params,&amp;nbsp;headers=headers,&amp;nbsp;timeout=20)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_json&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【网络异常】商品&amp;nbsp;{goods_id}&amp;nbsp;请求失败：{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;判断返回状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;resp_json.get(&amp;quot;error_code&amp;quot;)&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;resp_json.get(&amp;quot;reason&amp;quot;,&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【获取失败】商品&amp;nbsp;{goods_id}&amp;nbsp;,错误信息：{err_msg}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;resp_json.get(&amp;quot;item&amp;quot;,&amp;nbsp;{})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【警告】商品&amp;nbsp;{goods_id}&amp;nbsp;返回数据为空&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;整理核心字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;:&amp;nbsp;float(item_data.get(&amp;quot;price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item_data.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;orginal_price&amp;quot;:&amp;nbsp;float(item_data.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item_data.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;num&amp;quot;,&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;brand&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;detail_url&amp;quot;:&amp;nbsp;item_data.get(&amp;quot;detail_url&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;capture_ts&amp;quot;:&amp;nbsp;int(time.time()),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;capture_time&amp;quot;:&amp;nbsp;time.strftime(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;,&amp;nbsp;time.localtime())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;提取多规格SKU信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;skus_raw&amp;nbsp;=&amp;nbsp;item_data.get(&amp;quot;skus&amp;quot;,&amp;nbsp;{}).get(&amp;quot;sku&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;skus_raw:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_info&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_id&amp;quot;:&amp;nbsp;sku.get(&amp;quot;sku_id&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_price&amp;quot;:&amp;nbsp;float(sku.get(&amp;quot;price&amp;quot;,0))&amp;nbsp;if&amp;nbsp;sku.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_origin_price&amp;quot;:&amp;nbsp;float(sku.get(&amp;quot;orginal_price&amp;quot;,0))&amp;nbsp;if&amp;nbsp;sku.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_prop&amp;quot;:&amp;nbsp;sku.get(&amp;quot;properties_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_stock&amp;quot;:&amp;nbsp;sku.get(&amp;quot;quantity&amp;quot;,0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list.append(sku_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result[&amp;quot;sku_list&amp;quot;]&amp;nbsp;=&amp;nbsp;sku_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;2. 数据库初始化，存储历史快照&lt;/h3&gt;&lt;p&gt;使用轻量 sqlite 保存每一轮采集的数据，一张表存商品快照，一张表存规格快照，方便后续对比分析。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22def%20init_database()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(DB_FILE)%5Cn%20%20%20%20cur%20%3D%20conn.cursor()%5Cn%20%20%20%20%23%20%E5%95%86%E5%93%81%E4%B8%BB%E8%A1%A8%EF%BC%8C%E6%AF%8F%E4%B8%80%E6%AC%A1%E9%87%87%E9%9B%86%E7%94%9F%E6%88%90%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%5Cn%20%20%20%20cur.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20goods_snapshot%20(%5Cn%20%20%20%20%20%20%20%20id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%2C%5Cn%20%20%20%20%20%20%20%20num_iid%20TEXT%20NOT%20NULL%2C%5Cn%20%20%20%20%20%20%20%20title%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20orginal_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20stock%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20brand%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20shop_name%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20detail_url%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20capture_ts%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20capture_time%20TEXT%5Cn%20%20%20%20)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;)%5Cn%20%20%20%20%23%20SKU%E8%A7%84%E6%A0%BC%E5%AD%90%E8%A1%A8%EF%BC%8C%E4%BF%9D%E5%AD%98%E6%AF%8F%E4%B8%AA%E8%A7%84%E6%A0%BC%E7%9A%84%E4%BB%B7%E6%A0%BC%E5%BA%93%E5%AD%98%5Cn%20%20%20%20cur.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20goods_sku_snapshot%20(%5Cn%20%20%20%20%20%20%20%20id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%2C%5Cn%20%20%20%20%20%20%20%20parent_snapshot_id%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20sku_id%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20sku_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20sku_origin_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20sku_prop%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20sku_stock%20INTEGER%5Cn%20%20%20%20)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;)%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%5Cndef%20save_to_db(goods_data%3A%20dict)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%8A%8A%E5%8D%95%E6%AC%A1%E9%87%87%E9%9B%86%E7%9A%84%E5%95%86%E5%93%81%E6%95%B0%E6%8D%AE%E5%AD%98%E5%85%A5%E6%95%B0%E6%8D%AE%E5%BA%93%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(DB_FILE)%5Cn%20%20%20%20cur%20%3D%20conn.cursor()%5Cn%20%20%20%20cur.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20INSERT%20INTO%20goods_snapshot%20%5Cn%20%20%20%20(num_iid%2Ctitle%2Cprice%2Corginal_price%2Cstock%2Cbrand%2Cshop_name%2Cdetail_url%2Ccapture_ts%2Ccapture_time)%5Cn%20%20%20%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%2C(%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22num_iid%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22title%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22orginal_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22stock%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22brand%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22shop_name%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22detail_url%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22capture_ts%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20goods_data%5B%5C%22capture_time%5C%22%5D%5Cn%20%20%20%20))%5Cn%20%20%20%20snapshot_id%20%3D%20cur.lastrowid%5Cn%20%20%20%20%23%20%E5%86%99%E5%85%A5sku%E5%AD%90%E8%A1%A8%5Cn%20%20%20%20for%20sku%20in%20goods_data%5B%5C%22sku_list%5C%22%5D%3A%5Cn%20%20%20%20%20%20%20%20cur.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20%20%20%20%20INSERT%20INTO%20goods_sku_snapshot%5Cn%20%20%20%20%20%20%20%20(parent_snapshot_id%2Csku_id%2Csku_price%2Csku_origin_price%2Csku_prop%2Csku_stock)%5Cn%20%20%20%20%20%20%20%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5Cn%20%20%20%20%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%2C(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20snapshot_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_id%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_origin_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_prop%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_stock%5C%22%5D%5Cn%20%20%20%20%20%20%20%20))%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;init_database():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;初始化数据库表&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(DB_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;商品主表，每一次采集生成一条记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;goods_snapshot&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;AUTOINCREMENT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;num_iid&amp;nbsp;TEXT&amp;nbsp;NOT&amp;nbsp;NULL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;orginal_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;brand&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shop_name&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;detail_url&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capture_ts&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capture_time&amp;nbsp;TEXT
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SKU规格子表，保存每个规格的价格库存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;goods_sku_snapshot&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;AUTOINCREMENT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parent_snapshot_id&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_id&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_origin_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_prop&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_stock&amp;nbsp;INTEGER
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()

def&amp;nbsp;save_to_db(goods_data:&amp;nbsp;dict):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;把单次采集的商品数据存入数据库&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(DB_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_snapshot&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(num_iid,title,price,orginal_price,stock,brand,shop_name,detail_url,capture_ts,capture_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?,?,?,?,?)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;,(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;num_iid&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;title&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;orginal_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;stock&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;brand&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;shop_name&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;detail_url&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;capture_ts&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_data[&amp;quot;capture_time&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;snapshot_id&amp;nbsp;=&amp;nbsp;cur.lastrowid
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;写入sku子表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;goods_data[&amp;quot;sku_list&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_sku_snapshot
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(parent_snapshot_id,sku_id,sku_price,sku_origin_price,sku_prop,sku_stock)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;,(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;snapshot_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_id&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_origin_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_prop&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_stock&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;3. 异动检测逻辑：对比上一轮数据，识别价格、库存变化&lt;/h3&gt;&lt;p&gt;每次新拿到数据，查询数据库里该商品最近一条历史记录，做差值比对，输出变动提醒。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22def%20get_last_snapshot(num_iid%3A%20str)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%8E%B7%E5%8F%96%E8%AF%A5%E5%95%86%E5%93%81%E4%B8%8A%E4%B8%80%E6%AC%A1%E9%87%87%E9%9B%86%E7%9A%84%E5%BF%AB%E7%85%A7%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(DB_FILE)%5Cn%20%20%20%20cur%20%3D%20conn.cursor()%5Cn%20%20%20%20cur.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20SELECT%20price%2Corginal_price%2Cstock%20FROM%20goods_snapshot%20%5Cn%20%20%20%20WHERE%20num_iid%20%3D%20%3F%20ORDER%20BY%20capture_ts%20DESC%20LIMIT%201%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%2C(num_iid%2C))%5Cn%20%20%20%20row%20%3D%20cur.fetchone()%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20if%20row%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20return%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22price%5C%22%3A%20row%5B0%5D%2C%5Cn%20%20%20%20%20%20%20%20%5C%22orginal_price%5C%22%3A%20row%5B1%5D%2C%5Cn%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20row%5B2%5D%5Cn%20%20%20%20%7D%5Cn%5Cndef%20detect_change(last_data%2C%20new_data)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%A3%80%E6%B5%8B%E5%95%86%E5%93%81%E6%98%AF%E5%90%A6%E5%8F%91%E7%94%9F%E5%BC%82%E5%8A%A8%EF%BC%8C%E8%BF%94%E5%9B%9E%E5%8F%98%E5%8A%A8%E4%BF%A1%E6%81%AF%E5%88%97%E8%A1%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20change_msg%20%3D%20%5B%5D%5Cn%20%20%20%20if%20last_data%20is%20None%3A%5Cn%20%20%20%20%20%20%20%20change_msg.append(%5C%22%E9%A6%96%E6%AC%A1%E9%87%87%E9%9B%86%EF%BC%8C%E6%97%A0%E5%8E%86%E5%8F%B2%E5%AF%B9%E6%AF%94%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20change_msg%5Cn%20%20%20%20%23%20%E4%BB%B7%E6%A0%BC%E5%8F%98%E5%8C%96%5Cn%20%20%20%20if%20abs(new_data%5B%5C%22price%5C%22%5D%20-%20last_data%5B%5C%22price%5C%22%5D)%20%3E%200.01%3A%5Cn%20%20%20%20%20%20%20%20change_msg.append(f%5C%22%E4%BB%B7%E6%A0%BC%E5%8F%98%E5%8A%A8%EF%BC%9A%7Blast_data%5B&amp;#39;price&amp;#39;%5D%7D%20%E2%86%92%20%7Bnew_data%5B&amp;#39;price&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20if%20abs(new_data%5B%5C%22orginal_price%5C%22%5D%20-%20last_data%5B%5C%22orginal_price%5C%22%5D)%20%3E0.01%3A%5Cn%20%20%20%20%20%20%20%20change_msg.append(f%5C%22%E5%8E%9F%E4%BB%B7%E5%8F%98%E5%8A%A8%EF%BC%9A%7Blast_data%5B&amp;#39;orginal_price&amp;#39;%5D%7D%20%E2%86%92%20%7Bnew_data%5B&amp;#39;orginal_price&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%23%20%E5%BA%93%E5%AD%98%E5%8F%98%E5%8C%96%5Cn%20%20%20%20if%20new_data%5B%5C%22stock%5C%22%5D%20!%3D%20last_data%5B%5C%22stock%5C%22%5D%3A%5Cn%20%20%20%20%20%20%20%20change_msg.append(f%5C%22%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%EF%BC%9A%7Blast_data%5B&amp;#39;stock&amp;#39;%5D%7D%20%E2%86%92%20%7Bnew_data%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20return%20change_msg%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;get_last_snapshot(num_iid:&amp;nbsp;str):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;获取该商品上一次采集的快照&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(DB_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cur.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;price,orginal_price,stock&amp;nbsp;FROM&amp;nbsp;goods_snapshot&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&amp;nbsp;num_iid&amp;nbsp;=&amp;nbsp;?&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;capture_ts&amp;nbsp;DESC&amp;nbsp;LIMIT&amp;nbsp;1
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;,(num_iid,))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;row&amp;nbsp;=&amp;nbsp;cur.fetchone()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;row&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;:&amp;nbsp;row[0],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;orginal_price&amp;quot;:&amp;nbsp;row[1],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;row[2]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

def&amp;nbsp;detect_change(last_data,&amp;nbsp;new_data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;检测商品是否发生异动，返回变动信息列表&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msg&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;last_data&amp;nbsp;is&amp;nbsp;None:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msg.append(&amp;quot;首次采集，无历史对比&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;change_msg
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;价格变化
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;abs(new_data[&amp;quot;price&amp;quot;]&amp;nbsp;-&amp;nbsp;last_data[&amp;quot;price&amp;quot;])&amp;nbsp;&amp;gt;&amp;nbsp;0.01:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msg.append(f&amp;quot;价格变动：{last_data[&amp;#39;price&amp;#39;]}&amp;nbsp;→&amp;nbsp;{new_data[&amp;#39;price&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;abs(new_data[&amp;quot;orginal_price&amp;quot;]&amp;nbsp;-&amp;nbsp;last_data[&amp;quot;orginal_price&amp;quot;])&amp;nbsp;&amp;gt;0.01:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msg.append(f&amp;quot;原价变动：{last_data[&amp;#39;orginal_price&amp;#39;]}&amp;nbsp;→&amp;nbsp;{new_data[&amp;#39;orginal_price&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;库存变化
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;new_data[&amp;quot;stock&amp;quot;]&amp;nbsp;!=&amp;nbsp;last_data[&amp;quot;stock&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;change_msg.append(f&amp;quot;库存变动：{last_data[&amp;#39;stock&amp;#39;]}&amp;nbsp;→&amp;nbsp;{new_data[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;change_msg&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;4. 监控主循环 + 简单数据分析导出&lt;/h3&gt;&lt;p&gt;定时循环监控，同时提供函数可以把历史数据导出 Excel，做后续数据分析。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22def%20run_monitor_round()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%89%A7%E8%A1%8C%E4%B8%80%E8%BD%AE%E5%AE%8C%E6%95%B4%E7%9B%91%E6%8E%A7%E4%BB%BB%E5%8A%A1%5C%22%5C%22%5C%22%5Cn%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%20%E5%BC%80%E5%A7%8B%E4%B8%80%E8%BD%AE%E7%9B%91%E6%8E%A7%20%7Btime.strftime(&amp;#39;%25Y-%25m-%25d%20%25H%3A%25M%3A%25S&amp;#39;)%7D%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20for%20goods_id%20in%20MONITOR_GOODS_ID_LIST%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%E6%AD%A3%E5%9C%A8%E5%A4%84%E7%90%86%E5%95%86%E5%93%81%3A%7Bgoods_id%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20new_info%20%3D%20fetch_goods_detail(goods_id)%5Cn%20%20%20%20%20%20%20%20if%20not%20new_info%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20last_info%20%3D%20get_last_snapshot(goods_id)%5Cn%20%20%20%20%20%20%20%20changes%20%3D%20detect_change(last_info%2C%20new_info)%5Cn%20%20%20%20%20%20%20%20if%20len(changes)%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%20%E6%A3%80%E6%B5%8B%E5%88%B0%E5%BC%82%E5%8A%A8%E3%80%91%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20for%20m%20in%20changes%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%20%3E%3E%20%7Bm%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9C%85%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%E6%97%A0%E5%8F%98%E5%8C%96%E3%80%91%E5%BD%93%E5%89%8D%E4%BB%B7%E6%A0%BC%3A%7Bnew_info%5B&amp;#39;price&amp;#39;%5D%7D%2C%E5%BA%93%E5%AD%98%3A%7Bnew_info%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20save_to_db(new_info)%5Cn%5Cndef%20export_analysis_excel(out_file%3D%5C%22.%2Fgoods_monitor_report.xlsx%5C%22)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%AF%BC%E5%87%BA%E5%85%A8%E9%83%A8%E5%8E%86%E5%8F%B2%E5%BF%AB%E7%85%A7%E5%88%B0Excel%EF%BC%8C%E7%94%A8%E4%BA%8E%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(DB_FILE)%5Cn%20%20%20%20df_main%20%3D%20pd.read_sql(%5C%22SELECT%20*%20FROM%20goods_snapshot%5C%22%2C%20conn)%5Cn%20%20%20%20df_sku%20%3D%20pd.read_sql(%5C%22SELECT%20*%20FROM%20goods_sku_snapshot%5C%22%2C%20conn)%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20with%20pd.ExcelWriter(out_file)%20as%20writer%3A%5Cn%20%20%20%20%20%20%20%20df_main.to_excel(writer%2C%20sheet_name%3D%5C%22%E5%95%86%E5%93%81%E4%B8%BB%E5%BF%AB%E7%85%A7%5C%22%2C%20index%3DFalse)%5Cn%20%20%20%20%20%20%20%20df_sku.to_excel(writer%2C%20sheet_name%3D%5C%22%E8%A7%84%E6%A0%BC%E5%BF%AB%E7%85%A7%5C%22%2C%20index%3DFalse)%5Cn%20%20%20%20print(f%5C%22%E2%9C%85%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E6%8A%A5%E8%A1%A8%E5%B7%B2%E5%AF%BC%E5%87%BA%EF%BC%9A%7Bout_file%7D%5C%22)%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20init_database()%5Cn%20%20%20%20print(%5C%22%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AE%8C%E6%88%90%EF%BC%8C%E5%90%AF%E5%8A%A8%E7%9B%91%E6%8E%A7%E5%BE%AA%E7%8E%AF%EF%BC%8C%E6%8C%89Ctrl%2BC%E5%81%9C%E6%AD%A2%E7%A8%8B%E5%BA%8F%5C%22)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20run_monitor_round()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%8F%AF%E9%80%89%EF%BC%9A%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%E5%AF%BC%E5%87%BA%EF%BC%8C%E6%B3%A8%E9%87%8A%E6%8E%89%E5%B0%B1%E4%B8%8D%E4%BC%9A%E6%AF%8F%E6%AC%A1%E5%AF%BC%E5%87%BA%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20export_analysis_excel()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20time.sleep(MONITOR_INTERVAL)%5Cn%20%20%20%20except%20KeyboardInterrupt%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%E7%A8%8B%E5%BA%8F%E6%89%8B%E5%8A%A8%E9%80%80%E5%87%BA%EF%BC%8C%E7%BB%93%E6%9D%9F%E7%9B%91%E6%8E%A7%5C%22)%5Cn%20%20%20%20%20%20%20%20%23%20%E9%80%80%E5%87%BA%E5%89%8D%E5%AF%BC%E5%87%BA%E4%B8%80%E4%BB%BD%E6%8A%A5%E8%A1%A8%5Cn%20%20%20%20%20%20%20%20export_analysis_excel()%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;run_monitor_round():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;执行一轮完整监控任务&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n==========&amp;nbsp;开始一轮监控&amp;nbsp;{time.strftime(&amp;#39;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;#39;)}&amp;nbsp;==========&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;goods_id&amp;nbsp;in&amp;nbsp;MONITOR_GOODS_ID_LIST:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n正在处理商品:{goods_id}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new_info&amp;nbsp;=&amp;nbsp;fetch_goods_detail(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;new_info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_info&amp;nbsp;=&amp;nbsp;get_last_snapshot(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;changes&amp;nbsp;=&amp;nbsp;detect_change(last_info,&amp;nbsp;new_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(changes)&amp;nbsp;&amp;gt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️【商品{goods_id}&amp;nbsp;检测到异动】&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;m&amp;nbsp;in&amp;nbsp;changes:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;&amp;nbsp;&amp;gt;&amp;gt;&amp;nbsp;{m}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;✅【商品{goods_id}无变化】当前价格:{new_info[&amp;#39;price&amp;#39;]},库存:{new_info[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_db(new_info)

def&amp;nbsp;export_analysis_excel(out_file=&amp;quot;./goods_monitor_report.xlsx&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;导出全部历史快照到Excel，用于数据分析&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(DB_FILE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_main&amp;nbsp;=&amp;nbsp;pd.read_sql(&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;goods_snapshot&amp;quot;,&amp;nbsp;conn)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_sku&amp;nbsp;=&amp;nbsp;pd.read_sql(&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;goods_sku_snapshot&amp;quot;,&amp;nbsp;conn)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;pd.ExcelWriter(out_file)&amp;nbsp;as&amp;nbsp;writer:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_main.to_excel(writer,&amp;nbsp;sheet_name=&amp;quot;商品主快照&amp;quot;,&amp;nbsp;index=False)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_sku.to_excel(writer,&amp;nbsp;sheet_name=&amp;quot;规格快照&amp;quot;,&amp;nbsp;index=False)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;✅数据分析报表已导出：{out_file}&amp;quot;)

if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_database()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;初始化完成，启动监控循环，按Ctrl+C停止程序&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;run_monitor_round()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;可选：执行一次导出，注释掉就不会每次导出
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;export_analysis_excel()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(MONITOR_INTERVAL)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;KeyboardInterrupt:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n程序手动退出，结束监控&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;退出前导出一份报表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;export_analysis_excel()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;使用说明&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将代码中&lt;code&gt;ACCESS_KEY&lt;/code&gt;、&lt;code&gt;ACCESS_SECRET&lt;/code&gt;替换成你自己的凭证；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;修改&lt;code&gt;MONITOR_GOODS_ID_LIST&lt;/code&gt;填入你需要监控的商品编号；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;MONITOR_INTERVAL&lt;/code&gt;代表轮询间隔，建议不要设置太短，避免频繁请求；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;运行脚本，程序会循环执行监控，按&lt;code&gt;Ctrl+C&lt;/code&gt;结束程序，结束时自动导出 Excel 报表。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;运行之后，本地会生成&lt;code&gt;goods_monitor.db&lt;/code&gt;数据库文件，保存全部历史快照，每次采集到价格、库存变动，控制台会打印告警信息。导出的 Excel 可以用来做价格趋势统计、库存波动复盘。&lt;/p&gt;&lt;h2&gt;OpenClaw 如何赋能这套监控系统&lt;/h2&gt;&lt;p&gt;很多人写监控脚本，难点不在于写请求代码，而在于&lt;strong&gt;工程化调度、异常容错、任务管理&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;OpenClaw 可以把上面的业务逻辑封装成可复用的任务能力：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;定时调度托管&lt;/strong&gt;：不用自己写 while 循环 sleep，交给调度引擎，配置执行周期，程序崩溃自动重启重试；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常容错处理&lt;/strong&gt;：遇到网络超时、数据为空、返回异常，自动记录错误日志，不会因为单个商品失败导致整个监控任务挂掉；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警扩展&lt;/strong&gt;：检测到价格 / 库存异动后，可以对接消息推送，把变动消息直接发送到企业微信、钉钉，不用盯着控制台看输出；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析能力增强&lt;/strong&gt;：基于保存的历史快照，直接做趋势统计、批量报表生成，不用再手动写 pandas 处理逻辑；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;横向扩展&lt;/strong&gt;：后续增加更多平台商品监控，只需要新增数据获取逻辑，调度、存储、告警整套能力可以直接复用。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;简单来说：上面的 Python 脚本是业务逻辑原型，OpenClaw 负责把原型变成稳定长期跑在服务器上的生产级监控任务。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;拓展优化方向&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警推送&lt;/strong&gt;：检测到 changes 非空的时候，增加钉钉 / 企业微信 webhook，实现消息推送；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;阈值告警&lt;/strong&gt;：设置心理价位，当商品价格低于目标价格才触发提醒；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;增量过滤&lt;/strong&gt;：历史数据库越来越大，可以增加定时清理旧快照逻辑，只保留最近 N 天记录；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多任务分组&lt;/strong&gt;：把商品分成不同分组，不同分组设置不同监控频率，高优先级商品缩短轮询间隔；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可视化&lt;/strong&gt;：读取 sqlite 数据，用 matplotlib 绘制价格波动曲线图，直观查看历史价格走势。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;小结&lt;/h2&gt;&lt;p&gt;对于电商从业者、市场调研人员，手动维护商品信息成本极高。借助 OpenClaw 搭配成熟的数据获取能力，我们不需要深陷页面逆向、反爬对抗，把精力聚焦在业务本身：定时采集、历史归档、异动识别、数据分析。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Wed, 14 Oct 2026 09:18:47 +0800</pubDate></item><item><title>告别反复手工查价：用 Open‑Claw 搭建商品监控与数据分析小工具</title><link>https://www.shopapi.com.cn/?id=237</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品盯价、大促价格跟踪、库存异动监控、商品信息批量统计，Python 完整实战，可直接上手运行&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营、选品或者个人薅羊毛，经常会遇到一个头疼问题：需要持续关注一批商品的价格、库存、规格变化。如果每天手动点开页面一个个复制记录，不仅耗费大量时间，还很容易错过大促降价、库存清空这类关键变化。&lt;/p&gt;&lt;p&gt;直接写页面解析脚本又会碰到一堆麻烦：网页结构改版就全部失效、访问频繁触发风控限制、各种动态渲染内容拿不到完整规格信息，调试维护成本很高。&lt;/p&gt;&lt;p&gt;最近在实践中发现借助 Open‑Claw 的数据能力，只需要输入商品编号，就能够拿到结构化的商品全部信息，包含标题、原价、实时售价、多规格参数、库存、店铺信息、图集等完整内容，不用处理复杂网页解析，我们可以快速搭建一套本地监控 + 数据分析脚本，完成定时采集、异动提醒、历史数据留存与简单统计分析。&lt;/p&gt;&lt;p&gt;本文完整演示从数据获取、持久化存储、异动告警，再到简单数据分析的全流程，代码可以直接复制修改使用。&lt;/p&gt;&lt;h2&gt;整体实现思路&lt;/h2&gt;&lt;p&gt;整套工具分为 4 个核心模块：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;5&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E6%95%B0%E6%8D%AE%E8%8E%B7%E5%8F%96%E6%A8%A1%E5%9D%97%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22foIDMZ-1791788655142%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;数据获取模块&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;数据获取模块&quot;&gt;数据获取模块&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：输入商品编号，拿到标准化结构化商品数据，包含全部规格 SKU、价格、库存、品牌、图片、店铺等字段；支持缓存加速，减少重复请求消耗。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据存储模块&lt;/strong&gt;：每次采集带上时间戳，保存到本地 csv 文件，留存时序历史记录，不需要部署数据库，普通 Excel 就可以打开查看历史变化。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异动检测模块&lt;/strong&gt;：对比本次与上一轮采集记录，识别价格涨跌、库存变动，控制台输出告警，后续可以拓展企业微信、钉钉消息推送。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析模块&lt;/strong&gt;：基于本地历史文件，做价格波动统计、多商品横向对比，输出简单分析结果，辅助选品和竞品研判。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;环境准备&lt;/h2&gt;&lt;p&gt;依赖基础库，执行安装：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;4&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22pip%20install%20requests%20pandas%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;完整代码实战&lt;/h2&gt;&lt;h3&gt;1. 基础数据获取封装&lt;/h3&gt;&lt;blockquote&gt;&lt;p&gt;把网络请求逻辑封装，只传入商品编号，返回清洗之后的字典对象，同时处理异常情况（商品不存在、网络异常等）&lt;/p&gt;&lt;/blockquote&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cnimport%20requests%5Cnimport%20time%5Cnimport%20pandas%20as%20pd%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cnclass%20GoodsDataClient%3A%5Cn%20%20%20%20def%20__init__(self%2C%20key%2C%20secret)%3A%5Cn%20%20%20%20%20%20%20%20self.key%20%3D%20key%5Cn%20%20%20%20%20%20%20%20self.secret%20%3D%20secret%5Cn%20%20%20%20%20%20%20%20self.base_url%20%3D%20%5C%22https%3A%2F%2Fapi%E2%80%91gw.onebound.cn%2Fjd%2Fitem_get_pro%2F%5C%22%5Cn%5Cn%20%20%20%20def%20get_goods_info(self%2C%20goods_id)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E6%A0%B9%E6%8D%AE%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20self.key%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20self.secret%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22cache%5C%22%3A%20%5C%22yes%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22Accept%E2%80%91Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(self.base_url%2C%20params%3Dparams%2C%20headers%3Dheaders%2C%20timeout%3D15)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20json_data%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E7%BD%91%E7%BB%9C%E8%AF%B7%E6%B1%82%E5%BC%82%E5%B8%B8%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%BF%94%E5%9B%9E%E7%8A%B6%E6%80%81%5Cn%20%20%20%20%20%20%20%20if%20json_data.get(%5C%22error_code%5C%22)%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20err_msg%20%3D%20json_data.get(%5C%22reason%5C%22%2C%20%5C%22%E6%9C%AA%E7%9F%A5%E5%BC%82%E5%B8%B8%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE%E5%A4%B1%E8%B4%A5%EF%BC%9A%7Berr_msg%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%20%20%20%20item%20%3D%20json_data.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20not%20item%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%9C%AA%E8%8E%B7%E5%8F%96%E5%88%B0%E5%95%86%E5%93%81%E4%B8%BB%E4%BD%93%E4%BF%A1%E6%81%AF%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%20%20%20%20%23%20%E7%AE%80%E5%8D%95%E6%B8%85%E6%B4%97%E6%95%B4%E7%90%86%E6%A0%B8%E5%BF%83%E5%AD%97%E6%AE%B5%5Cn%20%20%20%20%20%20%20%20clean_data%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22crawl_time%5C%22%3A%20datetime.now().strftime(%5C%22%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20item.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20item.get(%5C%22title%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20item.get(%5C%22brand%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%3A%20float(item.get(%5C%22price%5C%22%2C%200))%20if%20item.get(%5C%22price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20float(item.get(%5C%22orginal_price%5C%22%2C0))%20if%20item.get(%5C%22orginal_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20item.get(%5C%22num%5C%22%2C0)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20item.get(%5C%22seller_info%5C%22%2C%7B%7D).get(%5C%22shop_name%5C%22%2C%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22detail_url%5C%22%3A%20item.get(%5C%22detail_url%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_list%5C%22%3A%20item.get(%5C%22skus%5C%22%2C%7B%7D).get(%5C%22sku%5C%22%2C%5B%5D)%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20return%20clean_data%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;time
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

class&amp;nbsp;GoodsDataClient:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;__init__(self,&amp;nbsp;key,&amp;nbsp;secret):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.key&amp;nbsp;=&amp;nbsp;key
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.secret&amp;nbsp;=&amp;nbsp;secret
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api‑gw.onebound.cn/jd/item_get_pro/&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;get_goods_info(self,&amp;nbsp;goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;根据商品编号获取商品结构化数据&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;self.key,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;self.secret,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;cache&amp;quot;:&amp;nbsp;&amp;quot;yes&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept‑Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(self.base_url,&amp;nbsp;params=params,&amp;nbsp;headers=headers,&amp;nbsp;timeout=15)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_data&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;网络请求异常：{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;判断返回状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;json_data.get(&amp;quot;error_code&amp;quot;)&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;json_data.get(&amp;quot;reason&amp;quot;,&amp;nbsp;&amp;quot;未知异常&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;获取数据失败：{err_msg}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item&amp;nbsp;=&amp;nbsp;json_data.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;未获取到商品主体信息&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;简单清洗整理核心字段
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;clean_data&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;crawl_time&amp;quot;:&amp;nbsp;datetime.now().strftime(&amp;quot;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;item.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;item.get(&amp;quot;title&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;item.get(&amp;quot;brand&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;:&amp;nbsp;float(item.get(&amp;quot;price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;float(item.get(&amp;quot;orginal_price&amp;quot;,0))&amp;nbsp;if&amp;nbsp;item.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;item.get(&amp;quot;num&amp;quot;,0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;item.get(&amp;quot;seller_info&amp;quot;,{}).get(&amp;quot;shop_name&amp;quot;,&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;detail_url&amp;quot;:&amp;nbsp;item.get(&amp;quot;detail_url&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_list&amp;quot;:&amp;nbsp;item.get(&amp;quot;skus&amp;quot;,{}).get(&amp;quot;sku&amp;quot;,[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;clean_data&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;2. 本地持久化与异动告警逻辑&lt;/h3&gt;&lt;p&gt;每次采集把数据追加写入 csv，同时对比上一条记录，发现价格或者库存发生明显变化，控制台打印告警提示。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22class%20GoodsMonitor%3A%5Cn%20%20%20%20def%20__init__(self%2C%20client%2C%20save_file%3D%5C%22goods_monitor.csv%5C%22)%3A%5Cn%20%20%20%20%20%20%20%20self.client%20%3D%20client%5Cn%20%20%20%20%20%20%20%20self.save_file%20%3D%20save_file%5Cn%5Cn%20%20%20%20def%20save_to_csv(self%2C%20data)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E4%BF%9D%E5%AD%98%E9%87%87%E9%9B%86%E8%AE%B0%E5%BD%95%E5%88%B0%E6%9C%AC%E5%9C%B0csv%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20df_new%20%3D%20pd.DataFrame(%5Bdata%5D)%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df_old%20%3D%20pd.read_csv(self.save_file%2C%20encoding%3D%5C%22utf%E2%80%918%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df_all%20%3D%20pd.concat(%5Bdf_old%2C%20df_new%5D%2C%20ignore_index%3DTrue)%5Cn%20%20%20%20%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df_all%20%3D%20df_new%5Cn%20%20%20%20%20%20%20%20df_all.to_csv(self.save_file%2C%20index%3DFalse%2C%20encoding%3D%5C%22utf%E2%80%918%5C%22)%5Cn%5Cn%20%20%20%20def%20check_change(self%2C%20new_data)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%AF%B9%E6%AF%94%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%A3%80%E6%B5%8B%E4%BB%B7%E6%A0%BC%E5%BA%93%E5%AD%98%E5%BC%82%E5%8A%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20goods_id%20%3D%20new_data%5B%5C%22goods_id%5C%22%5D%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(self.save_file%2C%20encoding%3D%5C%22utf%E2%80%918%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20his%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20goods_id%5D.tail(1)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20len(his)%20%3D%3D0%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20last%20%3D%20his.iloc%5B0%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20alert_list%20%3D%20%5B%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BB%B7%E6%A0%BC%E5%8F%98%E5%8A%A8%E8%B6%85%E8%BF%870.1%E5%85%83%E8%A7%A6%E5%8F%91%E6%8F%90%E9%86%92%5Cn%20%20%20%20%20%20%20%20%20%20%20%20price_diff%20%3D%20new_data%5B%5C%22current_price%5C%22%5D%20-%20float(last%5B%5C%22current_price%5C%22%5D)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20abs(price_diff)%20%3E%200.1%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alert_list.append(f%5C%22%E3%80%90%E4%BB%B7%E6%A0%BC%E5%8F%98%E5%8A%A8%E3%80%91%E5%8E%9F%E4%BB%B7%7Blast%5B&amp;#39;current_price&amp;#39;%5D%7D%20%E2%86%92%20%E7%8E%B0%E4%BB%B7%20%7Bnew_data%5B&amp;#39;current_price&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BA%93%E5%AD%98%E5%8F%91%E7%94%9F%E5%8F%98%E5%8C%96%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20int(new_data%5B%5C%22stock%5C%22%5D)%20!%3D%20int(last%5B%5C%22stock%5C%22%5D)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20alert_list.append(f%5C%22%E3%80%90%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%E3%80%91%E6%97%A7%E5%BA%93%E5%AD%98%7Blast%5B&amp;#39;stock&amp;#39;%5D%7D%20%E2%86%92%20%E5%BD%93%E5%89%8D%E5%BA%93%E5%AD%98%20%7Bnew_data%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20alert_list%5Cn%20%20%20%20%20%20%20%20except%20Exception%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20%5B%5D%5Cn%5Cn%20%20%20%20def%20monitor_one_goods(self%2C%20goods_id)%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%E5%BC%80%E5%A7%8B%E9%87%87%E9%9B%86%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20info%20%3D%20self.client.get_goods_info(goods_id)%5Cn%20%20%20%20%20%20%20%20if%20not%20info%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E9%87%87%E9%9B%86%E5%A4%B1%E8%B4%A5%EF%BC%8C%E8%B7%B3%E8%BF%87%E8%AF%A5%E5%95%86%E5%93%81%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20%23%20%E4%BF%9D%E5%AD%98%E8%AE%B0%E5%BD%95%5Cn%20%20%20%20%20%20%20%20self.save_to_csv(info)%5Cn%20%20%20%20%20%20%20%20%23%20%E6%A3%80%E6%9F%A5%E5%BC%82%E5%8A%A8%5Cn%20%20%20%20%20%20%20%20alerts%20%3D%20self.check_change(info)%5Cn%20%20%20%20%20%20%20%20if%20alerts%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E2%9A%A0%EF%B8%8F%E2%9A%A0%EF%B8%8F%E2%9A%A0%EF%B8%8F%E5%8F%91%E7%8E%B0%E5%BC%82%E5%8A%A8%E6%8F%90%E9%86%92%EF%BC%9A%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20for%20a%20in%20alerts%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Ct%7Ba%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%9C%AC%E6%AC%A1%E5%AF%B9%E6%AF%94%EF%BC%9A%E6%97%A0%E6%98%8E%E6%98%BE%E5%BC%82%E5%8A%A8%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%E6%A0%87%E9%A2%98%EF%BC%9A%7Binfo%5B&amp;#39;title&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%BD%93%E5%89%8D%E5%94%AE%E4%BB%B7%EF%BC%9A%7Binfo%5B&amp;#39;current_price&amp;#39;%5D%7D%20%EF%BD%9C%20%E5%8E%9F%E4%BB%B7%EF%BC%9A%7Binfo%5B&amp;#39;origin_price&amp;#39;%5D%7D%EF%BD%9C%E5%BA%93%E5%AD%98%EF%BC%9A%7Binfo%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20info%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;class&amp;nbsp;GoodsMonitor:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;__init__(self,&amp;nbsp;client,&amp;nbsp;save_file=&amp;quot;goods_monitor.csv&amp;quot;):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.client&amp;nbsp;=&amp;nbsp;client
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.save_file&amp;nbsp;=&amp;nbsp;save_file

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;save_to_csv(self,&amp;nbsp;data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;保存采集记录到本地csv&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new&amp;nbsp;=&amp;nbsp;pd.DataFrame([data])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_old&amp;nbsp;=&amp;nbsp;pd.read_csv(self.save_file,&amp;nbsp;encoding=&amp;quot;utf‑8&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all&amp;nbsp;=&amp;nbsp;pd.concat([df_old,&amp;nbsp;df_new],&amp;nbsp;ignore_index=True)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all&amp;nbsp;=&amp;nbsp;df_new
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_all.to_csv(self.save_file,&amp;nbsp;index=False,&amp;nbsp;encoding=&amp;quot;utf‑8&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;check_change(self,&amp;nbsp;new_data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;对比历史数据，检测价格库存异动&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id&amp;nbsp;=&amp;nbsp;new_data[&amp;quot;goods_id&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(self.save_file,&amp;nbsp;encoding=&amp;quot;utf‑8&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;his&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;goods_id].tail(1)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(his)&amp;nbsp;==0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last&amp;nbsp;=&amp;nbsp;his.iloc[0]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;价格变动超过0.1元触发提醒
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price_diff&amp;nbsp;=&amp;nbsp;new_data[&amp;quot;current_price&amp;quot;]&amp;nbsp;-&amp;nbsp;float(last[&amp;quot;current_price&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;abs(price_diff)&amp;nbsp;&amp;gt;&amp;nbsp;0.1:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert_list.append(f&amp;quot;【价格变动】原价{last[&amp;#39;current_price&amp;#39;]}&amp;nbsp;→&amp;nbsp;现价&amp;nbsp;{new_data[&amp;#39;current_price&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;库存发生变化
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;int(new_data[&amp;quot;stock&amp;quot;])&amp;nbsp;!=&amp;nbsp;int(last[&amp;quot;stock&amp;quot;]):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert_list.append(f&amp;quot;【库存变动】旧库存{last[&amp;#39;stock&amp;#39;]}&amp;nbsp;→&amp;nbsp;当前库存&amp;nbsp;{new_data[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;alert_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;[]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;monitor_one_goods(self,&amp;nbsp;goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n====开始采集商品&amp;nbsp;{goods_id}&amp;nbsp;====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;info&amp;nbsp;=&amp;nbsp;self.client.get_goods_info(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;采集失败，跳过该商品&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;保存记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.save_to_csv(info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;检查异动
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alerts&amp;nbsp;=&amp;nbsp;self.check_change(info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;alerts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;⚠️⚠️⚠️发现异动提醒：&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;a&amp;nbsp;in&amp;nbsp;alerts:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\t{a}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;本次对比：无明显异动&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品标题：{info[&amp;#39;title&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;当前售价：{info[&amp;#39;current_price&amp;#39;]}&amp;nbsp;｜&amp;nbsp;原价：{info[&amp;#39;origin_price&amp;#39;]}｜库存：{info[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;info&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;3. 简单数据分析函数&lt;/h3&gt;&lt;p&gt;读取本地全部历史记录，输出单商品价格统计，支持查看最高、最低、平均售价，也可以批量对比多款商品价格区间。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22def%20analysis_goods(file_name%2C%20target_goods_id%3DNone)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%EF%BC%8C%E6%89%93%E5%8D%B0%E4%BB%B7%E6%A0%BC%E7%BB%9F%E8%AE%A1%E4%BF%A1%E6%81%AF%5C%22%5C%22%5C%22%5Cn%20%20%20%20df%20%3D%20pd.read_csv(file_name%2C%20encoding%3D%5C%22utf%E2%80%918%5C%22)%5Cn%20%20%20%20if%20target_goods_id%3A%5Cn%20%20%20%20%20%20%20%20df%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20str(target_goods_id)%5D%5Cn%20%20%20%20%20%20%20%20if%20len(df)%20%3D%3D0%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%B2%A1%E6%9C%89%E6%89%BE%E5%88%B0%E8%AF%A5%E5%95%86%E5%93%81%E7%9A%84%E5%8E%86%E5%8F%B2%E9%87%87%E9%9B%86%E8%AE%B0%E5%BD%95%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%E5%95%86%E5%93%81%20%7Btarget_goods_id%7D%20%E5%8E%86%E5%8F%B2%E4%BB%B7%E6%A0%BC%E7%BB%9F%E8%AE%A1%3D%3D%3D%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E8%AE%B0%E5%BD%95%E6%80%BB%E6%9D%A1%E6%95%B0%EF%BC%9A%7Blen(df)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E6%9C%80%E4%BD%8E%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bdf%5B&amp;#39;current_price&amp;#39;%5D.min()%3A.2f%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E6%9C%80%E9%AB%98%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bdf%5B&amp;#39;current_price&amp;#39;%5D.max()%3A.2f%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E5%B9%B3%E5%9D%87%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bdf%5B&amp;#39;current_price&amp;#39;%5D.mean()%3A.2f%7D%5C%22)%5Cn%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%23%20%E5%85%A8%E9%83%A8%E5%95%86%E5%93%81%E7%AE%80%E5%8D%95%E6%B1%87%E6%80%BB%5Cn%20%20%20%20%20%20%20%20group%20%3D%20df.groupby(%5C%22goods_id%5C%22).agg(%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%3A%5B%5C%22min%5C%22%2C%5C%22max%5C%22%2C%5C%22mean%5C%22%2C%5C%22count%5C%22%5D%5Cn%20%20%20%20%20%20%20%20%7D).round(2)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%3D%3D%3D%3D%E5%85%A8%E9%83%A8%E7%9B%91%E6%8E%A7%E5%95%86%E5%93%81%E6%B1%87%E6%80%BB%E7%BB%9F%E8%AE%A1%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(group)%5Cn%20%20%20%20return%20df%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;analysis_goods(file_name,&amp;nbsp;target_goods_id=None):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;历史数据分析，打印价格统计信息&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(file_name,&amp;nbsp;encoding=&amp;quot;utf‑8&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;target_goods_id:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;str(target_goods_id)]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(df)&amp;nbsp;==0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;没有找到该商品的历史采集记录&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n========商品&amp;nbsp;{target_goods_id}&amp;nbsp;历史价格统计========&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;记录总条数：{len(df)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史最低售价：{df[&amp;#39;current_price&amp;#39;].min():.2f}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史最高售价：{df[&amp;#39;current_price&amp;#39;].max():.2f}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史平均售价：{df[&amp;#39;current_price&amp;#39;].mean():.2f}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;全部商品简单汇总
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;group&amp;nbsp;=&amp;nbsp;df.groupby(&amp;quot;goods_id&amp;quot;).agg({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;:[&amp;quot;min&amp;quot;,&amp;quot;max&amp;quot;,&amp;quot;mean&amp;quot;,&amp;quot;count&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}).round(2)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n====全部监控商品汇总统计====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(group)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;df&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;4. 主程序入口，定时循环监控&lt;/h3&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22if%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20%23%20%E5%A1%AB%E5%85%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E5%87%AD%E8%AF%81%5Cn%20%20%20%20MY_KEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%AF%86%E9%92%A5key%5C%22%5Cn%20%20%20%20MY_SECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%AF%86%E9%92%A5secret%5C%22%5Cn%5Cn%20%20%20%20client%20%3D%20GoodsDataClient(MY_KEY%2C%20MY_SECRET)%5Cn%20%20%20%20monitor%20%3D%20GoodsMonitor(client%2C%20save_file%3D%5C%22goods_monitor.csv%5C%22)%5Cn%5Cn%20%20%20%20%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%94%BE%E5%A4%9A%E4%B8%AA%E5%95%86%E5%93%81%5Cn%20%20%20%20watch_goods%20%3D%20%5B%5Cn%20%20%20%20%20%20%20%20%5C%2210335871600%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%23%20%5C%22%E5%A1%AB%E5%85%A5%E6%9B%B4%E5%A4%9A%E5%95%86%E5%93%81id%5C%22%5Cn%20%20%20%20%5D%5Cn%5Cn%20%20%20%20%23%20%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%EF%BC%8C%E8%AE%BE%E7%BD%AE%E9%97%B4%E9%9A%94%E6%97%B6%E9%97%B4%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E6%A0%B9%E6%8D%AE%E8%87%AA%E8%BA%AB%E9%85%8D%E9%A2%9D%E5%90%88%E7%90%86%E8%AE%BE%E7%BD%AE%EF%BC%8C%E4%B8%8D%E8%A6%81%E8%AF%B7%E6%B1%82%E8%BF%87%E4%BA%8E%E9%A2%91%E7%B9%81%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20for%20gid%20in%20watch_goods%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20monitor.monitor_one_goods(gid)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20time.sleep(4)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%E6%9C%AC%E8%BD%AE%E9%87%87%E9%9B%86%E5%AE%8C%E6%88%90%EF%BC%8C%E7%AD%89%E5%BE%85%E4%B8%8B%E4%B8%80%E8%BD%AE%E5%BE%AA%E7%8E%AF......%5C%22)%5Cn%20%20%20%20%20%20%20%20time.sleep(60*10)%20%23%2010%E5%88%86%E9%92%9F%E6%89%A7%E8%A1%8C%E4%B8%80%E8%BD%AE%5Cn%5Cn%20%20%20%20%20%20%20%20%23%20%E5%A6%82%E6%9E%9C%E9%9C%80%E8%A6%81%E6%89%A7%E8%A1%8C%E5%88%86%E6%9E%90%EF%BC%8C%E5%8F%AF%E4%BB%A5%E5%8F%96%E6%B6%88%E4%B8%8B%E9%9D%A2%E6%B3%A8%E9%87%8A%5Cn%20%20%20%20%20%20%20%20%23%20analysis_goods(%5C%22goods_monitor.csv%5C%22%2C%20watch_goods%5B0%5D)%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;填入自己的凭证
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的密钥key&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的密钥secret&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;client&amp;nbsp;=&amp;nbsp;GoodsDataClient(MY_KEY,&amp;nbsp;MY_SECRET)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor&amp;nbsp;=&amp;nbsp;GoodsMonitor(client,&amp;nbsp;save_file=&amp;quot;goods_monitor.csv&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;需要监控的商品编号列表，可以放多个商品
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;watch_goods&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;10335871600&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;&amp;quot;填入更多商品id&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;循环监控，设置间隔时间，单位秒，根据自身配额合理设置，不要请求过于频繁
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;watch_goods:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor.monitor_one_goods(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(4)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n本轮采集完成，等待下一轮循环......&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(60*10)&amp;nbsp;#&amp;nbsp;10分钟执行一轮

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;如果需要执行分析，可以取消下面注释
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;analysis_goods(&amp;quot;goods_monitor.csv&amp;quot;,&amp;nbsp;watch_goods[0])&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;使用说明与注意事项&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;获取商品编号&lt;/strong&gt;：商品详情网页链接中，&lt;code&gt;item.jd.com/&lt;/code&gt;后面那串数字就是 goods_id，复制填入监控列表即可。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;频率控制&lt;/strong&gt;：循环间隔不要设置过小，频繁请求会消耗额度，也容易触发限制，建议至少数分钟以上一轮。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;缓存开关&lt;/strong&gt;：代码中 cache 参数设置 yes，会优先读取缓存数据，速度更快；如果需要实时最新数据，可以改为 no。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常处理&lt;/strong&gt;：脚本已经做了基础异常捕获，网络波动、商品下架不会让整个程序直接崩溃。当商品下架或者编号错误时控制台会打印提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;拓展方向&lt;/strong&gt;
	&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;告警：检测到价格变动之后，可以对接企业微信 / 钉钉 webhook 推送消息，不用盯着控制台。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;可视化：基于 pandas+matplotlib，读取 csv 历史数据，画出商品价格走势图。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;定时任务：Linux/macOS 用 crontab、Windows 使用任务计划程序，把脚本后台长期运行。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;规格深度解析：返回结果中&lt;code&gt;sku_list&lt;/code&gt;包含全部规格的编号、价格、库存，我们可以二次处理做多规格分别监控。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ol&gt;&lt;h2&gt;拿到数据之后可以做哪些分析？&lt;/h2&gt;&lt;p&gt;有了持续沉淀的时序数据，就可以落地很多业务分析：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;大促价格研判&lt;/strong&gt;：对比历史最低价位，判断当前是不是好的入手时机，观察大促期间真实降价幅度，区分先涨价再打折的虚假活动。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;竞品对标&lt;/strong&gt;：持续跟踪同行多款竞品的调价节奏、库存状态，了解对手上新、清仓动作。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;库存预警&lt;/strong&gt;：监控爆款库存水位，当库存快速下跌，及时捕捉市场热度变化。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;规格分析&lt;/strong&gt;：解析多规格数据，看哪一个规格价格最有优势，哪些规格经常缺货。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;踩坑小结&lt;/h2&gt;&lt;p&gt;之前尝试直接页面解析的方案，经常遇到页面改版代码直接失效，动态 JS 渲染拿不全 SKU 规格，调试消耗大量时间。借助 Open‑Claw 拿到已经处理好的结构化数据之后，我们只需要专注业务逻辑：存储、告警、数据分析，不用再消耗精力对抗网页反爬和页面变更，小工具开发效率提升非常明显。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 12 Oct 2026 15:17:27 +0800</pubDate></item><item><title>Open‑Claw 实战｜告别页面逆向，快速搭建电商商品监控与数据分析完整方案</title><link>https://www.shopapi.com.cn/?id=236</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品跟踪、价格盯盘、库存状态巡检、历史价格统计分析；语言 Python3，适合开发、数据分析从业者阅读，可直接复制代码运行调试。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商相关数据分析的时候，最头疼的就是商品页面的数据获取。直接解析网页会遇到动态渲染、反访问、页面改版、规格页面逻辑复杂等一堆麻烦，每次页面改版，之前写好的解析脚本就要全部重写，大量时间消耗在页面适配上面，真正留给业务分析的时间反而很少。&lt;/p&gt;&lt;p&gt;很多同学会使用浏览器自动化工具去模拟访问，但又会碰到验证码、设备指纹、访问频率限制，部署到服务器运行的时候坑非常多。&lt;/p&gt;&lt;p&gt;今天分享一套基于 Open‑Claw 工具的实践方案，不需要处理网页逆向、不用维护页面选择器，直接拿到标准化的商品全量信息，完成&lt;strong&gt;定时监控、数据落盘、异常告警、历史数据分析&lt;/strong&gt;整套流程。&lt;/p&gt;&lt;p&gt;我们可以一次性拿到：商品标题、现价、原价、库存、全部规格信息、店铺信息、主图、发货地等完整内容，把精力聚焦业务逻辑，而不是和网页页面做对抗。&lt;/p&gt;&lt;h2&gt;整体业务目标&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E5%95%86%E5%93%81%E5%85%A8%E9%87%8F%E4%BF%A1%E6%81%AF%E5%BF%AB%E7%85%A7%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22CAUzjH-1791528394141%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;商品全量信息快照&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;商品全量信息快照&quot;&gt;商品全量信息快照&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：获取商品基础信息，包含多规格维度的数据。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;持续定时巡检&lt;/strong&gt;：周期性采集商品状态，记录每一次时间切片的数据，捕捉降价、涨价、缺货、规格变动。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;本地持久化存储&lt;/strong&gt;：将每次采集结果保存到 CSV，留存历史数据，用于后续统计分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常提醒&lt;/strong&gt;：价格低于预期阈值、库存清零，控制台输出告警。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;简易数据分析&lt;/strong&gt;：读取本地历史文件，做价格波动简单统计。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;环境准备&lt;/h2&gt;&lt;p&gt;依赖安装，执行下面命令：&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22pip%20install%20requests%20pandas%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;完整实现代码&lt;/h2&gt;&lt;p&gt;整体分为：封装数据获取类、字段解析、存储、告警、定时循环、数据分析几个模块。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;使用前需要替换凭证信息，以及填写你需要监控的商品编号。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20coding%3Autf-8%5Cnimport%20requests%5Cnimport%20time%5Cnimport%20csv%5Cnimport%20os%5Cnfrom%20datetime%20import%20datetime%5Cnimport%20pandas%20as%20pd%5Cn%5Cn%5Cnclass%20GoodsMonitor%3A%5Cn%20%20%20%20def%20__init__(self%2C%20access_key%2C%20access_secret)%3A%5Cn%20%20%20%20%20%20%20%20self.access_key%20%3D%20access_key%5Cn%20%20%20%20%20%20%20%20self.access_secret%20%3D%20access_secret%5Cn%20%20%20%20%20%20%20%20self.base_address%20%3D%20%5C%22https%3A%2F%2Fapi%E2%80%91gw.onebound.cn%2Fjd%2Fitem_get_pro%2F%5C%22%5Cn%20%20%20%20%20%20%20%20self.csv_file%20%3D%20%5C%22goods_monitor_record.csv%5C%22%5Cn%20%20%20%20%20%20%20%20self.init_csv()%5Cn%5Cn%20%20%20%20def%20init_csv(self)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%88%9D%E5%A7%8B%E5%8C%96%E5%AD%98%E5%82%A8%E6%96%87%E4%BB%B6%EF%BC%8C%E4%B8%8D%E5%AD%98%E5%9C%A8%E5%88%99%E5%88%9B%E5%BB%BA%E8%A1%A8%E5%A4%B4%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20if%20not%20os.path.exists(self.csv_file)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20headers%20%3D%20%5B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22crawl_time%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_title%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22stock_num%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22brand%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_list%5C%22%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20with%20open(self.csv_file%2C%20%5C%22w%5C%22%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22%2C%20newline%3D%5C%22%5C%22)%20as%20f%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20writer%20%3D%20csv.writer(f)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20writer.writerow(headers)%5Cn%5Cn%20%20%20%20def%20get_raw_data(self%2C%20goods_id)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E5%8E%9F%E5%A7%8B%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20self.access_key%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20self.access_secret%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22cache%5C%22%3A%20%5C%22no%5C%22%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22Accept%E2%80%91Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.base_address%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20params%3Dparams%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20headers%3Dheaders%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20timeout%3D25%5Cn%20%20%20%20%20%20%20%20%20%20%20%20)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20resp.json()%5Cn%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%3A%20%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20def%20parse_raw(self%2C%20raw_data)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E6%B8%85%E6%B4%97%E8%A7%A3%E6%9E%90%E5%8E%9F%E5%A7%8B%E8%BF%94%E5%9B%9E%E5%86%85%E5%AE%B9%EF%BC%8C%E6%8F%90%E5%8F%96%E4%B8%9A%E5%8A%A1%E9%9C%80%E8%A6%81%E5%AD%97%E6%AE%B5%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20if%20not%20raw_data%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20err_code%20%3D%20raw_data.get(%5C%22error_code%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20err_code%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20err_info%20%3D%20raw_data.get(%5C%22error%5C%22%2C%20%5C%22%E8%8E%B7%E5%8F%96%E6%95%B0%E6%8D%AE%E5%A4%B1%E8%B4%A5%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E6%95%B0%E6%8D%AE%E5%BC%82%E5%B8%B8%EF%BC%9A%7Berr_info%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%20%20%20%20item_info%20%3D%20raw_data.get(%5C%22item%5C%22%2C%20%7B%7D)%5Cn%20%20%20%20%20%20%20%20if%20not%20item_info%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20%20%20%20%20sku_data%20%3D%20item_info.get(%5C%22skus%5C%22%2C%20%7B%7D).get(%5C%22sku%5C%22%2C%20%5B%5D)%5Cn%20%20%20%20%20%20%20%20sku_text%20%3D%20%5C%22%7C%7C%5C%22.join(%5Bf&amp;#39;%7Bx.get(%5C%22properties_name%5C%22)%7D%3A%7Bx.get(%5C%22price%5C%22)%7D&amp;#39;%20for%20x%20in%20sku_data%5D)%5Cn%5Cn%20%20%20%20%20%20%20%20result%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22crawl_time%5C%22%3A%20datetime.now().strftime(%5C%22%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_id%5C%22%3A%20item_info.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22goods_title%5C%22%3A%20item_info.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22current_price%5C%22%3A%20float(item_info.get(%5C%22price%5C%22%2C%200))%20if%20item_info.get(%5C%22price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22origin_price%5C%22%3A%20float(item_info.get(%5C%22orginal_price%5C%22%2C%200))%20if%20item_info.get(%5C%22orginal_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22stock_num%5C%22%3A%20int(item_info.get(%5C%22num%5C%22%2C%200))%20if%20item_info.get(%5C%22num%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20item_info.get(%5C%22brand%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20item_info.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_list%5C%22%3A%20sku_text%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20return%20result%5Cn%5Cn%20%20%20%20def%20save_record(self%2C%20data)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%86%99%E5%85%A5%E6%9C%AC%E5%9C%B0csv%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20with%20open(self.csv_file%2C%20%5C%22a%5C%22%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22%2C%20newline%3D%5C%22%5C%22)%20as%20f%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20writer%20%3D%20csv.writer(f)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20row%20%3D%20%5B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22crawl_time%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22goods_id%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22goods_title%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22current_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22origin_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22stock_num%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22brand%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22shop_name%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20data%5B%5C%22sku_list%5C%22%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20writer.writerow(row)%5Cn%5Cn%20%20%20%20def%20check_warn(self%2C%20data%2C%20warn_price)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%91%8A%E8%AD%A6%E9%80%BB%E8%BE%91%EF%BC%9A%E4%BB%B7%E6%A0%BC%E4%BD%8E%E4%BA%8E%E9%98%88%E5%80%BC%E3%80%81%E5%BA%93%E5%AD%98%E4%B8%BA0%E8%A7%A6%E5%8F%91%E6%8F%90%E9%86%92%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20if%20data%5B%5C%22current_price%5C%22%5D%20%3C%3D%20warn_price%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22*70)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E3%80%90%E4%BB%B7%E6%A0%BC%E9%A2%84%E8%AD%A6%E3%80%91%E5%95%86%E5%93%81%EF%BC%9A%7Bdata%5B&amp;#39;goods_title&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E7%8E%B0%E4%BB%B7%20%7Bdata%5B&amp;#39;current_price&amp;#39;%5D%7D%EF%BC%8C%E4%BD%8E%E4%BA%8E%E7%9B%91%E6%8E%A7%E9%98%88%E5%80%BC%20%7Bwarn_price%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22*70)%5Cn%20%20%20%20%20%20%20%20if%20data%5B%5C%22stock_num%5C%22%5D%20%3C%3D%200%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22*70)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%F0%9F%94%B4%E3%80%90%E5%BA%93%E5%AD%98%E5%91%8A%E8%AD%A6%E3%80%91%E5%95%86%E5%93%81%20%7Bdata%5B&amp;#39;goods_title&amp;#39;%5D%7D%20%E5%BD%93%E5%89%8D%E7%BC%BA%E8%B4%A7%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%3D%5C%22*70)%5Cn%5Cn%20%20%20%20def%20single_task(self%2C%20goods_id%2C%20warn_price)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%8D%95%E6%AC%A1%E7%9B%91%E6%8E%A7%E4%BB%BB%E5%8A%A1%EF%BC%9A%E8%8E%B7%E5%8F%96%E2%80%91%E8%A7%A3%E6%9E%90%E2%80%91%E5%AD%98%E5%82%A8%E2%80%91%E5%91%8A%E8%AD%A6%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20raw%20%3D%20self.get_raw_data(goods_id)%5Cn%20%20%20%20%20%20%20%20parsed%20%3D%20self.parse_raw(raw)%5Cn%20%20%20%20%20%20%20%20if%20parsed%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20self.save_record(parsed)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20self.check_warn(parsed%2C%20warn_price)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9C%85%20%7Bparsed%5B&amp;#39;crawl_time&amp;#39;%5D%7D%20%E5%AE%8C%E6%88%90%E8%AE%B0%E5%BD%95%EF%BC%9A%7Bparsed%5B&amp;#39;goods_title&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9D%8C%20%E5%95%86%E5%93%81%20%7Bgoods_id%7D%20%E8%8E%B7%E5%8F%96%E4%BF%A1%E6%81%AF%E5%A4%B1%E8%B4%A5%5C%22)%5Cn%5Cn%20%20%20%20def%20run_monitor_loop(self%2C%20watch_list%2C%20interval%3D60*30)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E5%BE%AA%E7%8E%AF%E6%8C%81%E7%BB%AD%E7%9B%91%E6%8E%A7%5Cn%20%20%20%20%20%20%20%20watch_list%3A%20%5B(%E5%95%86%E5%93%81ID%2C%E9%A2%84%E8%AD%A6%E4%BB%B7%E6%A0%BC)%2C...%5D%5Cn%20%20%20%20%20%20%20%20interval%EF%BC%9A%E5%BE%AA%E7%8E%AF%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E7%A4%BA%E4%BE%8B30%E5%88%86%E9%92%9F%3D1800%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20print(%5C%22%3E%3E%3E%3E%3E%3E%E5%90%AF%E5%8A%A8%E5%95%86%E5%93%81%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%3C%3C%3C%3C%3C%3C%5C%22)%5Cn%20%20%20%20%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20for%20goods_id%2C%20warn_price%20in%20watch_list%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20self.single_task(goods_id%2C%20warn_price)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time.sleep(3)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%E7%AD%89%E5%BE%85%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%87%87%E9%9B%86%EF%BC%8C%E9%97%B4%E9%9A%94%20%7Binterval%2F%2F60%7D%20%E5%88%86%E9%92%9F%5C%5Cn%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20time.sleep(interval)%5Cn%5Cn%20%20%20%20def%20analysis_history(self)%3A%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22%E7%AE%80%E5%8D%95%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%EF%BC%8C%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0csv%E5%81%9A%E4%BB%B7%E6%A0%BC%E7%BB%9F%E8%AE%A1%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20if%20not%20os.path.exists(self.csv_file)%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%9A%82%E6%97%A0%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%E6%96%87%E4%BB%B6%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%5Cn%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(self.csv_file%2C%20encoding%3D%5C%22utf%E2%80%918%E2%80%91sig%5C%22)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1%E5%88%86%E6%9E%90%3D%3D%3D%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20goods_group%20%3D%20df.groupby(%5C%22goods_title%5C%22)%5B%5C%22current_price%5C%22%5D.agg(%5B%5C%22min%5C%22%2C%5C%22max%5C%22%2C%5C%22mean%5C%22%2C%5C%22count%5C%22%5D)%5Cn%20%20%20%20%20%20%20%20print(goods_group)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn%E6%AF%8F%E4%B8%80%E4%BB%B6%E5%95%86%E5%93%81%E6%9C%80%E4%BD%8E%E3%80%81%E6%9C%80%E9%AB%98%E3%80%81%E5%B9%B3%E5%9D%87%E4%BB%B7%E6%A0%BC%EF%BC%8C%E9%87%87%E9%9B%86%E6%80%BB%E6%AC%A1%E6%95%B0%E5%A6%82%E4%B8%8A%5C%22)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%E9%85%8D%E7%BD%AE%E5%8C%BA%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%20%20%20%20MY_KEY%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E4%B8%BA%E4%BD%A0%E7%9A%84%E5%87%AD%E8%AF%81key%5C%22%5Cn%20%20%20%20MY_SECRET%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E4%B8%BA%E4%BD%A0%E7%9A%84%E5%87%AD%E8%AF%81secret%5C%22%5Cn%5Cn%20%20%20%20%23%20%E5%BE%85%E7%9B%91%E6%8E%A7%E5%88%97%E8%A1%A8%EF%BC%8C%E6%A0%BC%E5%BC%8F(%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%EF%BC%8C%E4%BB%B7%E6%A0%BC%E9%A2%84%E8%AD%A6%E7%BA%BF)%5Cn%20%20%20%20WATCH_GOODS%20%3D%20%5B%5Cn%20%20%20%20%20%20%20%20(%5C%2210335871600%5C%22%2C%20220)%2C%5Cn%20%20%20%20%20%20%20%20(%5C%2210057467958584%5C%22%2C%20600)%5Cn%20%20%20%20%5D%5Cn%20%20%20%20LOOP_INTERVAL%20%3D%201800%20%20%23%20%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E8%BF%99%E9%87%8C30%E5%88%86%E9%92%9F%E8%B7%91%E4%B8%80%E8%BD%AE%5Cn%20%20%20%20%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cn%20%20%20%20monitor%20%3D%20GoodsMonitor(MY_KEY%2C%20MY_SECRET)%5Cn%5Cn%20%20%20%20%23%201.%E6%89%A7%E8%A1%8C%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%EF%BC%8C%E5%BC%80%E5%90%AF%E5%90%8E%E4%BC%9A%E6%8C%81%E7%BB%AD%E8%BF%90%E8%A1%8C%5Cn%20%20%20%20monitor.run_monitor_loop(WATCH_GOODS%2C%20interval%3DLOOP_INTERVAL)%5Cn%5Cn%20%20%20%20%23%202.%E5%8F%AA%E5%81%9A%E4%B8%80%E6%AC%A1%E9%87%87%E9%9B%86%EF%BC%8C%E6%B5%8B%E8%AF%95%E7%94%A8%5Cn%20%20%20%20%23%20monitor.single_task(%5C%2210335871600%5C%22%2C200)%5Cn%5Cn%20%20%20%20%23%203.%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0%E5%8E%86%E5%8F%B2%E5%81%9A%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%5Cn%20%20%20%20%23%20monitor.analysis_history()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;coding:utf-8
import&amp;nbsp;requests
import&amp;nbsp;time
import&amp;nbsp;csv
import&amp;nbsp;os
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd


class&amp;nbsp;GoodsMonitor:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;__init__(self,&amp;nbsp;access_key,&amp;nbsp;access_secret):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.access_key&amp;nbsp;=&amp;nbsp;access_key
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.access_secret&amp;nbsp;=&amp;nbsp;access_secret
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.base_address&amp;nbsp;=&amp;nbsp;&amp;quot;https://api‑gw.onebound.cn/jd/item_get_pro/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.csv_file&amp;nbsp;=&amp;nbsp;&amp;quot;goods_monitor_record.csv&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.init_csv()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;init_csv(self):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;初始化存储文件，不存在则创建表头&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;os.path.exists(self.csv_file):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;crawl_time&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_title&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock_num&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_list&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(self.csv_file,&amp;nbsp;&amp;quot;w&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;,&amp;nbsp;newline=&amp;quot;&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writer&amp;nbsp;=&amp;nbsp;csv.writer(f)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writer.writerow(headers)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;get_raw_data(self,&amp;nbsp;goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;获取商品原始结构化数据&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;self.access_key,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;self.access_secret,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;cache&amp;quot;:&amp;nbsp;&amp;quot;no&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept‑Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.base_address,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params=params,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers=headers,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout=25
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;网络异常:&amp;nbsp;{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;parse_raw(self,&amp;nbsp;raw_data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;清洗解析原始返回内容，提取业务需要字段&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;raw_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_code&amp;nbsp;=&amp;nbsp;raw_data.get(&amp;quot;error_code&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;err_code&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_info&amp;nbsp;=&amp;nbsp;raw_data.get(&amp;quot;error&amp;quot;,&amp;nbsp;&amp;quot;获取数据失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;数据异常：{err_info}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_info&amp;nbsp;=&amp;nbsp;raw_data.get(&amp;quot;item&amp;quot;,&amp;nbsp;{})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_data&amp;nbsp;=&amp;nbsp;item_info.get(&amp;quot;skus&amp;quot;,&amp;nbsp;{}).get(&amp;quot;sku&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_text&amp;nbsp;=&amp;nbsp;&amp;quot;||&amp;quot;.join([f&amp;#39;{x.get(&amp;quot;properties_name&amp;quot;)}:{x.get(&amp;quot;price&amp;quot;)}&amp;#39;&amp;nbsp;for&amp;nbsp;x&amp;nbsp;in&amp;nbsp;sku_data])

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;crawl_time&amp;quot;:&amp;nbsp;datetime.now().strftime(&amp;quot;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_id&amp;quot;:&amp;nbsp;item_info.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;goods_title&amp;quot;:&amp;nbsp;item_info.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;current_price&amp;quot;:&amp;nbsp;float(item_info.get(&amp;quot;price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item_info.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;origin_price&amp;quot;:&amp;nbsp;float(item_info.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item_info.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock_num&amp;quot;:&amp;nbsp;int(item_info.get(&amp;quot;num&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item_info.get(&amp;quot;num&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;item_info.get(&amp;quot;brand&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;item_info.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_list&amp;quot;:&amp;nbsp;sku_text
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;save_record(self,&amp;nbsp;data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;写入本地csv历史记录&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(self.csv_file,&amp;nbsp;&amp;quot;a&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;,&amp;nbsp;newline=&amp;quot;&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writer&amp;nbsp;=&amp;nbsp;csv.writer(f)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;row&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;crawl_time&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;goods_id&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;goods_title&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;current_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;origin_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;stock_num&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;brand&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;shop_name&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;sku_list&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;writer.writerow(row)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;check_warn(self,&amp;nbsp;data,&amp;nbsp;warn_price):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;告警逻辑：价格低于阈值、库存为0触发提醒&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;data[&amp;quot;current_price&amp;quot;]&amp;nbsp;&amp;lt;=&amp;nbsp;warn_price:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;*70)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️【价格预警】商品：{data[&amp;#39;goods_title&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;现价&amp;nbsp;{data[&amp;#39;current_price&amp;#39;]}，低于监控阈值&amp;nbsp;{warn_price}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;*70)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;data[&amp;quot;stock_num&amp;quot;]&amp;nbsp;&amp;lt;=&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;*70)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;🔴【库存告警】商品&amp;nbsp;{data[&amp;#39;goods_title&amp;#39;]}&amp;nbsp;当前缺货&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;=&amp;quot;*70)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;single_task(self,&amp;nbsp;goods_id,&amp;nbsp;warn_price):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;单次监控任务：获取‑解析‑存储‑告警&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raw&amp;nbsp;=&amp;nbsp;self.get_raw_data(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed&amp;nbsp;=&amp;nbsp;self.parse_raw(raw)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;parsed:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.save_record(parsed)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.check_warn(parsed,&amp;nbsp;warn_price)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;✅&amp;nbsp;{parsed[&amp;#39;crawl_time&amp;#39;]}&amp;nbsp;完成记录：{parsed[&amp;#39;goods_title&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;❌&amp;nbsp;商品&amp;nbsp;{goods_id}&amp;nbsp;获取信息失败&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;run_monitor_loop(self,&amp;nbsp;watch_list,&amp;nbsp;interval=60*30):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;循环持续监控
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;watch_list:&amp;nbsp;[(商品ID,预警价格),...]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;interval：循环间隔，单位秒，示例30分钟=1800
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;启动商品循环监控&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;goods_id,&amp;nbsp;warn_price&amp;nbsp;in&amp;nbsp;watch_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;self.single_task(goods_id,&amp;nbsp;warn_price)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(3)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n等待下一轮采集，间隔&amp;nbsp;{interval//60}&amp;nbsp;分钟\n&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(interval)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;def&amp;nbsp;analysis_history(self):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;简单历史数据分析，读取本地csv做价格统计&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;os.path.exists(self.csv_file):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;暂无历史记录文件&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(self.csv_file,&amp;nbsp;encoding=&amp;quot;utf‑8‑sig&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n========历史数据统计分析========&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_group&amp;nbsp;=&amp;nbsp;df.groupby(&amp;quot;goods_title&amp;quot;)[&amp;quot;current_price&amp;quot;].agg([&amp;quot;min&amp;quot;,&amp;quot;max&amp;quot;,&amp;quot;mean&amp;quot;,&amp;quot;count&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(goods_group)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n每一件商品最低、最高、平均价格，采集总次数如上&amp;quot;)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;=========配置区========
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;替换为你的凭证key&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MY_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;替换为你的凭证secret&amp;quot;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;待监控列表，格式(商品编号，价格预警线)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WATCH_GOODS&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(&amp;quot;10335871600&amp;quot;,&amp;nbsp;220),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(&amp;quot;10057467958584&amp;quot;,&amp;nbsp;600)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LOOP_INTERVAL&amp;nbsp;=&amp;nbsp;1800&amp;nbsp;&amp;nbsp;#&amp;nbsp;循环监控间隔，单位秒，这里30分钟跑一轮
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;======================

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor&amp;nbsp;=&amp;nbsp;GoodsMonitor(MY_KEY,&amp;nbsp;MY_SECRET)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;1.执行循环监控，开启后会持续运行
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor.run_monitor_loop(WATCH_GOODS,&amp;nbsp;interval=LOOP_INTERVAL)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;2.只做一次采集，测试用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;monitor.single_task(&amp;quot;10335871600&amp;quot;,200)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.读取本地历史做数据分析
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;monitor.analysis_history()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;代码说明&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;初始化文件&lt;/strong&gt;：程序第一次运行自动生成&lt;code&gt;goods_monitor_record.csv&lt;/code&gt;，所有采集快照全部保存在这里，Excel 可以直接打开查看。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据获取与解析&lt;/strong&gt;：工具返回已经结构化的商品信息，我们只需要从中筛选业务需要字段，不需要写 Xpath、CSS 选择器，页面改版不会影响业务代码。同时做异常捕获，单次获取失败不会让整个程序直接崩溃退出。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警机制&lt;/strong&gt;：当商品价格低于设置阈值，或者库存为 0，控制台打印提醒，可以在此基础上拓展推送能力，比如对接企业微信、邮件消息推送。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;循环监控&lt;/strong&gt;：&lt;code&gt;run_monitor_loop&lt;/code&gt;会按照设定的间隔周期循环巡检多件商品，服务器后台运行就可以实现 7×24 小时盯盘。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析模块&lt;/strong&gt;：&lt;code&gt;analysis_history&lt;/code&gt;读取本地历史记录，使用 pandas 统计每个商品历史最低价、最高价、平均价格，方便做价格趋势研判。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;⚠️注意：不要把间隔设置过小，频繁请求会触发访问限制，业务场景建议至少 10‑30 分钟以上采集一轮。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;拓展方向&lt;/h2&gt;&lt;h3&gt;1. 存储升级：替换 CSV 为数据库&lt;/h3&gt;&lt;p&gt;如果监控商品数量很多，CSV 读写效率会下降，可以把存储逻辑修改为 SQLite / MySQL，增加批量分析、筛选查询能力。示例写入 SQL 片段：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20%E4%BC%AA%E4%BB%A3%E7%A0%81%E7%A4%BA%E6%84%8F%5Cn%23%20insert_sql%20%3D%20%5C%22%5C%22%5C%22%5Cn%23%20INSERT%20INTO%20goods_snapshot(goods_id%2Ctitle%2Cprice%2Corigin_price%2Cstock%2Ccrawl_time)%5Cn%23%20VALUES%20(%25s%2C%25s%2C%25s%2C%25s%2C%25s%2C%25s)%5Cn%23%20%5C%22%5C%22%5C%22%5Cn%23%20cursor.execute(insert_sql%2C(parsed%5B%5C%22goods_id%5C%22%5D%2Cparsed%5B%5C%22goods_title%5C%22%5D%2Cparsed%5B%5C%22current_price%5C%22%5D%2Cparsed%5B%5C%22origin_price%5C%22%5D%2Cparsed%5B%5C%22stock_num%5C%22%5D%2Cparsed%5B%5C%22crawl_time%5C%22%5D))%5Cn%23%20conn.commit()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;伪代码示意
#&amp;nbsp;insert_sql&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
#&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_snapshot(goods_id,title,price,origin_price,stock,crawl_time)
#&amp;nbsp;VALUES&amp;nbsp;(%s,%s,%s,%s,%s,%s)
#&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
#&amp;nbsp;cursor.execute(insert_sql,(parsed[&amp;quot;goods_id&amp;quot;],parsed[&amp;quot;goods_title&amp;quot;],parsed[&amp;quot;current_price&amp;quot;],parsed[&amp;quot;origin_price&amp;quot;],parsed[&amp;quot;stock_num&amp;quot;],parsed[&amp;quot;crawl_time&amp;quot;]))
#&amp;nbsp;conn.commit()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3&gt;2. 消息推送扩展&lt;/h3&gt;&lt;p&gt;现在告警只是控制台打印，可以接入企业微信机器人、邮件、钉钉，一旦触发价格 / 库存变化，直接推送消息到手机，不用一直盯着控制台。&lt;/p&gt;&lt;h3&gt;3. 可视化&lt;/h3&gt;&lt;p&gt;基于 pandas + matplotlib 读取历史记录，绘制商品价格变化曲线图，直观看历史价格波动。&lt;/p&gt;&lt;h3&gt;4. 多平台扩展&lt;/h3&gt;&lt;p&gt;这套思路可以迁移到其他电商品类，只需要调整解析字段，监控逻辑、存储、告警代码完全复用。&lt;/p&gt;&lt;h2&gt;踩坑总结&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;价格字段说明&lt;/strong&gt;：返回的基础价格为主商品价格，如果要看各个规格真实价格，需要读取 sku_list 字段，不同规格会存在价格差异。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;缓存控制&lt;/strong&gt;：代码中设置&lt;code&gt;cache=no&lt;/code&gt;，目的是拿到最新的实时快照；测试调试的时候可以打开缓存，减少消耗。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常处理&lt;/strong&gt;：商品下架、编号错误、网络抖动，程序会捕获错误并打印日志，不会直接退出循环监控任务。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;服务器部署&lt;/strong&gt;：Linux 后台运行可以使用&lt;code&gt;nohup python monitor.py &amp;amp;&lt;/code&gt;方式，注意配置好 Python 环境与依赖包。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;小结&lt;/h2&gt;&lt;p&gt;借助 Open‑Claw 工具，我们绕开了复杂网页解析工作，把重心放在业务监控逻辑上。几十行代码就完成商品定时监控、历史数据留存、异常告警、数据分析整套工程，不用反复适配页面结构，极大降低维护成本。&lt;/p&gt;&lt;p&gt;对于个人爱好者、小团队做竞品盯盘、价格跟踪、库存巡检，这套方案足够满足大部分场景。在此基础上，还可以继续拓展报表输出、消息推送、多商品批量管理等能力。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Fri, 09 Oct 2026 14:51:30 +0800</pubDate></item><item><title>告别复杂页面解析，OpenClaw 快速搭建电商商品监控与数据分析脚本</title><link>https://www.shopapi.com.cn/?id=235</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：个人比价、商家竞品跟踪、价格库存异动监测，无需处理页面渲染、验证码等棘手问题，Python 完整实战教程。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商相关开发或者个人薅羊毛的时候，经常有这样的需求：持续跟踪一批商品的价格变动、库存状态、规格参数变化，做历史数据分析、降价提醒、竞品对比。&lt;/p&gt;&lt;p&gt;如果自己手写页面解析，会遇到一堆麻烦：网页结构经常改版、动态渲染、频繁弹出人机校验，IP 访问频率高还容易被限制。调试解析规则就要耗费大量时间，经常今天写好的脚本，过几天页面一改直接全部失效。&lt;/p&gt;&lt;p&gt;最近体验了 OpenClaw 这套工具，它把商品页面的各类信息做了标准化输出，我们只需要传入商品编号，就能拿到结构化的标题、售价、原价、SKU 规格、库存、店铺信息、图片等全部内容，把精力放在业务逻辑：监控、存储、分析，不用再折腾页面解析。&lt;/p&gt;&lt;p&gt;本文就完整演示如何基于 OpenClaw，实现&lt;strong&gt;定时商品监控、数据本地存储、简单异动分析&lt;/strong&gt;，完整代码可以直接复制运行。&lt;/p&gt;&lt;h2&gt;整体思路&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;准备好工具的&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E8%BA%AB%E4%BB%BD%E5%87%AD%E8%AF%81%EF%BC%88%E5%AF%86%E9%92%A5%E4%BF%A1%E6%81%AF%EF%BC%89%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22cgb0Nd-1791517513095%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;身份凭证（密钥信息）&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;身份凭证（密钥信息）&quot;&gt;身份凭证（密钥信息）&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;，拿到需要监控的商品 ID；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;编写获取商品结构化信息的基础函数；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;增加异常捕获，处理商品下架、网络波动等各类异常情况；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;将每次采集到的数据存入本地文件，留存历史记录；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;增加定时循环，实现持续监控；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;简单数据分析：对比前后两次数据，识别降价、库存变动。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;提示：运行脚本前，请先安装依赖库&lt;/p&gt;&lt;/blockquote&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22code%22%3A%22pip%20install%20requests%20pandas%20schedule%5Cn%22%2C%22classes%22%3Anull%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-cke-widget-upcasted=&quot;1&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;schedule&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;完整 Python 实战代码&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cn%5C%22%5C%22%5C%22%5CnOpenClaw%20%E7%94%B5%E5%95%86%E5%95%86%E5%93%81%E7%9B%91%E6%8E%A7%26%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E8%84%9A%E6%9C%AC%5Cn%E5%8A%9F%E8%83%BD%EF%BC%9A%E5%95%86%E5%93%81%E4%BF%A1%E6%81%AF%E8%8E%B7%E5%8F%96%E3%80%81%E5%BC%82%E5%B8%B8%E5%AE%B9%E9%94%99%E3%80%81%E6%9C%AC%E5%9C%B0%E4%BF%9D%E5%AD%98%E5%8E%86%E5%8F%B2%E3%80%81%E4%BB%B7%E6%A0%BC%E5%BA%93%E5%AD%98%E5%BC%82%E5%8A%A8%E6%A3%80%E6%B5%8B%5Cn%5C%22%5C%22%5C%22%5Cnimport%20requests%5Cnimport%20time%5Cnimport%20pandas%20as%20pd%5Cnimport%20schedule%5Cnimport%20json%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%20%E9%85%8D%E7%BD%AE%E5%8C%BA%E5%9F%9F%EF%BC%8C%E8%AF%B7%E8%87%AA%E8%A1%8C%E4%BF%AE%E6%94%B9%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%23%20%E4%BD%A0%E7%9A%84%E4%B8%AA%E4%BA%BA%E5%87%AD%E8%AF%81%E4%BF%A1%E6%81%AF%5CnACCESS_KEY%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E6%88%90%E4%BD%A0%E7%9A%84key%5C%22%5CnACCESS_SECRET%20%3D%20%5C%22%E6%9B%BF%E6%8D%A2%E6%88%90%E4%BD%A0%E7%9A%84secret%5C%22%5Cn%5Cn%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%E5%88%97%E8%A1%A8%EF%BC%8C%E5%8F%AF%E4%BB%A5%E6%B7%BB%E5%8A%A0%E5%A4%9A%E4%B8%AA%E5%95%86%E5%93%81ID%5CnMONITOR_GOODS_ID%20%3D%20%5B%5Cn%20%20%20%20%5C%2210335871600%5C%22%2C%5Cn%20%20%20%20%5C%2210057467958584%5C%22%5Cn%5D%5Cn%5Cn%23%20%E7%9B%91%E6%8E%A7%E9%97%B4%E9%9A%94%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E4%B8%8D%E8%A6%81%E8%AE%BE%E7%BD%AE%E8%BF%87%E5%B0%8F%EF%BC%8C%E9%81%BF%E5%85%8D%E8%B0%83%E7%94%A8%E8%BF%87%E4%BA%8E%E9%A2%91%E7%B9%81%5CnMONITOR_INTERVAL%20%3D%2060%20*%2010%5Cn%5Cn%23%20%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E4%BF%9D%E5%AD%98%E6%96%87%E4%BB%B6%5CnSAVE_FILE%20%3D%20%5C%22goods_monitor_history.csv%5C%22%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cn%5Cndef%20get_goods_detail(goods_id%3A%20str)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E8%8E%B7%E5%8F%96%E5%8D%95%E4%B8%AA%E5%95%86%E5%93%81%E7%BB%93%E6%9E%84%E5%8C%96%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20goods_id%3A%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20return%3A%20%E6%88%90%E5%8A%9F%E8%BF%94%E5%9B%9E%E5%AD%97%E5%85%B8%EF%BC%8C%E5%A4%B1%E8%B4%A5%E8%BF%94%E5%9B%9ENone%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20base_url%20%3D%20%5C%22https%3A%2F%2Fapi-gw.onebound.cn%2Fjd%2Fitem_get_pro%2F%5C%22%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20ACCESS_KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20ACCESS_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%2C%5Cn%20%20%20%20%20%20%20%20%5C%22cache%5C%22%3A%20%5C%22no%5C%22%2C%20%20%20%23%20no%E8%8E%B7%E5%8F%96%E6%9C%80%E6%96%B0%E6%95%B0%E6%8D%AE%EF%BC%8Cyes%E8%AF%BB%E5%8F%96%E7%BC%93%E5%AD%98%E5%8A%A0%E5%BF%AB%E9%80%9F%E5%BA%A6%5Cn%20%20%20%20%20%20%20%20%5C%22result_type%5C%22%3A%20%5C%22json%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22Accept-Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(base_url%2C%20params%3Dparams%2C%20headers%3Dheaders%2C%20timeout%3D20)%5Cn%20%20%20%20%20%20%20%20resp_json%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20error_code%20%3D%20resp_json.get(%5C%22error_code%5C%22)%5Cn%5Cn%20%20%20%20%20%20%20%20%23%200000%E4%BB%A3%E8%A1%A8%E6%AD%A3%E5%B8%B8%E6%8B%BF%E5%88%B0%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20%20%20%20%20if%20error_code%20%3D%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item_data%20%3D%20resp_json.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20item_data%5Cn%20%20%20%20%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20err_msg%20%3D%20resp_json.get(%5C%22error%5C%22%2C%20%5C%22%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%E3%80%91%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%EF%BC%8C%E9%94%99%E8%AF%AF%E4%BF%A1%E6%81%AF%EF%BC%9A%7Berr_msg%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E3%80%90%E5%95%86%E5%93%81%7Bgoods_id%7D%E3%80%91%E7%BD%91%E7%BB%9C%E6%88%96%E8%A7%A3%E6%9E%90%E5%BC%82%E5%B8%B8%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20parse_item(raw_item%3A%20dict)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%8A%8A%E5%8E%9F%E5%A7%8B%E8%BF%94%E5%9B%9E%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%8F%90%E5%8F%96%E6%88%91%E4%BB%AC%E5%85%B3%E5%BF%83%E7%9A%84%E6%A0%B8%E5%BF%83%E5%AD%97%E6%AE%B5%5C%22%5C%22%5C%22%5Cn%20%20%20%20if%20not%20raw_item%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20out%20%3D%20%7B%7D%5Cn%20%20%20%20out%5B%5C%22crawl_time%5C%22%5D%20%3D%20datetime.now().strftime(%5C%22%25Y-%25m-%25d%20%25H%3A%25M%3A%25S%5C%22)%5Cn%20%20%20%20out%5B%5C%22goods_id%5C%22%5D%20%3D%20raw_item.get(%5C%22num_iid%5C%22)%5Cn%20%20%20%20out%5B%5C%22title%5C%22%5D%20%3D%20raw_item.get(%5C%22title%5C%22%2C%20%5C%22%5C%22)%5Cn%20%20%20%20out%5B%5C%22price%5C%22%5D%20%3D%20float(raw_item.get(%5C%22price%5C%22%2C%200))%20if%20raw_item.get(%5C%22price%5C%22)%20else%200%5Cn%20%20%20%20out%5B%5C%22original_price%5C%22%5D%20%3D%20float(raw_item.get(%5C%22orginal_price%5C%22%2C%200))%20if%20raw_item.get(%5C%22orginal_price%5C%22)%20else%200%5Cn%20%20%20%20out%5B%5C%22stock%5C%22%5D%20%3D%20raw_item.get(%5C%22num%5C%22%2C%200)%5Cn%20%20%20%20out%5B%5C%22shop_name%5C%22%5D%20%3D%20raw_item.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%5Cn%20%20%20%20out%5B%5C%22detail_url%5C%22%5D%20%3D%20raw_item.get(%5C%22detail_url%5C%22%2C%20%5C%22%5C%22)%5Cn%20%20%20%20%23%20sku%E8%A7%84%E6%A0%BC%E7%AE%80%E5%8D%95%E6%8F%90%E5%8F%96%5Cn%20%20%20%20sku_list%20%3D%20raw_item.get(%5C%22skus%5C%22%2C%20%7B%7D).get(%5C%22sku%5C%22%2C%20%5B%5D)%5Cn%20%20%20%20sku_info%20%3D%20%5B%5D%5Cn%20%20%20%20for%20sku%20in%20sku_list%3A%5Cn%20%20%20%20%20%20%20%20sku_info.append(%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_id%5C%22%3A%20sku.get(%5C%22sku_id%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22spec_name%5C%22%3A%20sku.get(%5C%22properties_name%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_price%5C%22%3A%20sku.get(%5C%22price%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_stock%5C%22%3A%20sku.get(%5C%22quantity%5C%22)%5Cn%20%20%20%20%20%20%20%20%7D)%5Cn%20%20%20%20out%5B%5C%22sku_info%5C%22%5D%20%3D%20json.dumps(sku_info%2C%20ensure_ascii%3DFalse)%5Cn%20%20%20%20return%20out%5Cn%5Cn%5Cndef%20save_to_csv(data_row%3A%20dict)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%8D%95%E6%9D%A1%E8%AE%B0%E5%BD%95%E8%BF%BD%E5%8A%A0%E4%BF%9D%E5%AD%98%E5%88%B0csv%E5%8E%86%E5%8F%B2%E6%96%87%E4%BB%B6%5C%22%5C%22%5C%22%5Cn%20%20%20%20df_new%20%3D%20pd.DataFrame(%5Bdata_row%5D)%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%23%20%E6%96%87%E4%BB%B6%E5%AD%98%E5%9C%A8%E5%B0%B1%E8%BF%BD%E5%8A%A0%EF%BC%8C%E4%B8%8D%E5%AD%98%E5%9C%A8%E6%96%B0%E5%BB%BA%5Cn%20%20%20%20%20%20%20%20with%20open(SAVE_FILE%2C%20%5C%22r%5C%22%2C%20encoding%3D%5C%22utf-8-sig%5C%22)%20as%20f%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20df_new.to_csv(SAVE_FILE%2C%20mode%3D%5C%22a%5C%22%2C%20header%3DFalse%2C%20index%3DFalse%2C%20encoding%3D%5C%22utf-8-sig%5C%22)%5Cn%20%20%20%20except%20FileNotFoundError%3A%5Cn%20%20%20%20%20%20%20%20df_new.to_csv(SAVE_FILE%2C%20mode%3D%5C%22w%5C%22%2C%20header%3DTrue%2C%20index%3DFalse%2C%20encoding%3D%5C%22utf-8-sig%5C%22)%5Cn%5Cn%5Cnlast_status%20%3D%20%7B%7D%20%20%23%20%E4%BF%9D%E5%AD%98%E4%B8%8A%E4%B8%80%E8%BD%AE%E9%87%87%E9%9B%86%E7%8A%B6%E6%80%81%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%BC%82%E5%8A%A8%E5%AF%B9%E6%AF%94%5Cn%5Cn%5Cndef%20monitor_task()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E4%B8%80%E8%BD%AE%E5%AE%8C%E6%95%B4%E7%9B%91%E6%8E%A7%E4%BB%BB%E5%8A%A1%EF%BC%8C%E5%BE%AA%E7%8E%AF%E6%89%80%E6%9C%89%E5%95%86%E5%93%81%5C%22%5C%22%5C%22%5Cn%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%E5%BC%80%E5%A7%8B%E4%B8%80%E8%BD%AE%E7%9B%91%E6%8E%A7%20%7Bdatetime.now().strftime(&amp;#39;%25Y-%25m-%25d%20%25H%3A%25M%3A%25S&amp;#39;)%7D%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20global%20last_status%5Cn%20%20%20%20for%20gid%20in%20MONITOR_GOODS_ID%3A%5Cn%20%20%20%20%20%20%20%20item_raw%20%3D%20get_goods_detail(gid)%5Cn%20%20%20%20%20%20%20%20parsed%20%3D%20parse_item(item_raw)%5Cn%20%20%20%20%20%20%20%20if%20not%20parsed%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20continue%5Cn%20%20%20%20%20%20%20%20save_to_csv(parsed)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%E3%80%90%7Bparsed%5B&amp;#39;title&amp;#39;%5D%5B0%3A25%5D%7D...%E3%80%91%E7%8E%B0%E4%BB%B7%EF%BC%9A%7Bparsed%5B&amp;#39;price&amp;#39;%5D%7D%20%E5%BA%93%E5%AD%98%EF%BC%9A%7Bparsed%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%5Cn%20%20%20%20%20%20%20%20%23%20%E5%92%8C%E4%B8%8A%E4%B8%80%E6%AC%A1%E6%95%B0%E6%8D%AE%E5%AF%B9%E6%AF%94%EF%BC%8C%E6%A3%80%E6%B5%8B%E5%BC%82%E5%8A%A8%5Cn%20%20%20%20%20%20%20%20prev%20%3D%20last_status.get(gid)%5Cn%20%20%20%20%20%20%20%20if%20prev%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E4%BB%B7%E6%A0%BC%E4%B8%8B%E8%B7%8C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20parsed%5B%5C%22price%5C%22%5D%20%3C%20prev%5B%5C%22price%5C%22%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%20%E4%BB%B7%E6%A0%BC%E5%BC%82%E5%8A%A8%E6%8F%90%E9%86%92%EF%BC%81%E5%95%86%E5%93%81%7Bgid%7D%E5%8F%91%E7%94%9F%E9%99%8D%E4%BB%B7%EF%BC%8C%E4%B8%8A%E6%AC%A1%7Bprev%5B&amp;#39;price&amp;#39;%5D%7D%EF%BC%8C%E5%BD%93%E5%89%8D%7Bparsed%5B&amp;#39;price&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%23%20%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%5Cn%20%20%20%20%20%20%20%20%20%20%20%20if%20parsed%5B%5C%22stock%5C%22%5D%20!%3D%20prev%5B%5C%22stock%5C%22%5D%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%20%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%E6%8F%90%E9%86%92%EF%BC%81%E5%95%86%E5%93%81%7Bgid%7D%E5%BA%93%E5%AD%98%E7%94%B1%7Bprev%5B&amp;#39;stock&amp;#39;%5D%7D%E5%8F%98%E6%9B%B4%E4%B8%BA%7Bparsed%5B&amp;#39;stock&amp;#39;%5D%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%23%20%E6%9B%B4%E6%96%B0%E8%AE%B0%E5%BD%95%5Cn%20%20%20%20%20%20%20%20last_status%5Bgid%5D%20%3D%20parsed%5Cn%20%20%20%20%20%20%20%20time.sleep(2)%5Cn%5Cn%5Cndef%20analysis_history()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E7%AE%80%E5%8D%95%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E7%A4%BA%E4%BE%8B%EF%BC%8C%E8%AF%BB%E5%8F%96%E6%9C%AC%E5%9C%B0%E5%8E%86%E5%8F%B2%EF%BC%8C%E6%89%93%E5%8D%B0%E6%AF%8F%E4%B8%AA%E5%95%86%E5%93%81%E4%BB%B7%E6%A0%BC%E5%8C%BA%E9%97%B4%5C%22%5C%22%5C%22%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20df%20%3D%20pd.read_csv(SAVE_FILE%2C%20encoding%3D%5C%22utf-8-sig%5C%22)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%5C%5Cn----------%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E7%BB%93%E6%9E%9C----------%5C%22)%5Cn%20%20%20%20%20%20%20%20for%20goods_id%20in%20df%5B%5C%22goods_id%5C%22%5D.unique()%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sub%20%3D%20df%5Bdf%5B%5C%22goods_id%5C%22%5D%20%3D%3D%20goods_id%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title%20%3D%20sub.iloc%5B0%5D%5B%5C%22title%5C%22%5D%5Cn%20%20%20%20%20%20%20%20%20%20%20%20min_p%20%3D%20sub%5B%5C%22price%5C%22%5D.min()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20max_p%20%3D%20sub%5B%5C%22price%5C%22%5D.max()%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E5%95%86%E5%93%81%EF%BC%9A%7Btitle%5B%3A30%5D%7D...%20%E5%8E%86%E5%8F%B2%E6%9C%80%E4%BD%8E%3A%7Bmin_p%7D%20%E5%8E%86%E5%8F%B2%E6%9C%80%E9%AB%98%3A%7Bmax_p%7D%EF%BC%8C%E5%85%B1%E9%87%87%E9%9B%86%7Blen(sub)%7D%E6%9D%A1%E8%AE%B0%E5%BD%95%5C%22)%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E6%9A%82%E6%97%A0%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%EF%BC%8C%E7%AD%89%E5%BE%85%E9%87%87%E9%9B%86%E5%AE%8C%E6%88%90%5C%22%2C%20e)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20%23%20%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%5Cn%20%20%20%20monitor_task()%5Cn%20%20%20%20analysis_history()%5Cn%5Cn%20%20%20%20%23%20%E8%AE%BE%E7%BD%AE%E5%AE%9A%E6%97%B6%E5%BE%AA%E7%8E%AF%E6%89%A7%E8%A1%8C%5Cn%20%20%20%20schedule.every(MONITOR_INTERVAL).seconds.do(monitor_task)%5Cn%20%20%20%20print(f%5C%22%5C%5Cn%E5%B7%B2%E5%90%AF%E5%8A%A8%E5%AE%9A%E6%97%B6%E7%9B%91%E6%8E%A7%EF%BC%8C%E6%AF%8F%20%7BMONITOR_INTERVAL%2F60%7D%E5%88%86%E9%92%9F%E6%89%A7%E8%A1%8C%E4%B8%80%E6%AC%A1%EF%BC%8CCtrl%2BC%E7%BB%88%E6%AD%A2%E7%A8%8B%E5%BA%8F%5C%22)%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20schedule.run_pending()%5Cn%20%20%20%20%20%20%20%20time.sleep(1)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
&amp;quot;&amp;quot;&amp;quot;
OpenClaw&amp;nbsp;电商商品监控&amp;amp;数据分析脚本
功能：商品信息获取、异常容错、本地保存历史、价格库存异动检测
&amp;quot;&amp;quot;&amp;quot;
import&amp;nbsp;requests
import&amp;nbsp;time
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
import&amp;nbsp;schedule
import&amp;nbsp;json
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

#&amp;nbsp;========================&amp;nbsp;配置区域，请自行修改&amp;nbsp;========================
#&amp;nbsp;你的个人凭证信息
ACCESS_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;替换成你的key&amp;quot;
ACCESS_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;替换成你的secret&amp;quot;

#&amp;nbsp;需要监控的商品编号列表，可以添加多个商品ID
MONITOR_GOODS_ID&amp;nbsp;=&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;10335871600&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;10057467958584&amp;quot;
]

#&amp;nbsp;监控间隔，单位秒，不要设置过小，避免调用过于频繁
MONITOR_INTERVAL&amp;nbsp;=&amp;nbsp;60&amp;nbsp;*&amp;nbsp;10

#&amp;nbsp;历史数据保存文件
SAVE_FILE&amp;nbsp;=&amp;nbsp;&amp;quot;goods_monitor_history.csv&amp;quot;
#&amp;nbsp;====================================================================


def&amp;nbsp;get_goods_detail(goods_id:&amp;nbsp;str):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获取单个商品结构化数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id:商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return:&amp;nbsp;成功返回字典，失败返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api-gw.onebound.cn/jd/item_get_pro/&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;ACCESS_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;ACCESS_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;cache&amp;quot;:&amp;nbsp;&amp;quot;no&amp;quot;,&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;no获取最新数据，yes读取缓存加快速度
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;result_type&amp;quot;:&amp;nbsp;&amp;quot;json&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept-Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(base_url,&amp;nbsp;params=params,&amp;nbsp;headers=headers,&amp;nbsp;timeout=20)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp_json&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;error_code&amp;nbsp;=&amp;nbsp;resp_json.get(&amp;quot;error_code&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;0000代表正常拿到数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;error_code&amp;nbsp;==&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;resp_json.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;item_data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;err_msg&amp;nbsp;=&amp;nbsp;resp_json.get(&amp;quot;error&amp;quot;,&amp;nbsp;&amp;quot;未知错误&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【商品{goods_id}】获取失败，错误信息：{err_msg}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【商品{goods_id}】网络或解析异常：{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;parse_item(raw_item:&amp;nbsp;dict):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;把原始返回数据，提取我们关心的核心字段&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;raw_item:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out&amp;nbsp;=&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;crawl_time&amp;quot;]&amp;nbsp;=&amp;nbsp;datetime.now().strftime(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;goods_id&amp;quot;]&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;num_iid&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;title&amp;quot;]&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;title&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;price&amp;quot;]&amp;nbsp;=&amp;nbsp;float(raw_item.get(&amp;quot;price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;raw_item.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;original_price&amp;quot;]&amp;nbsp;=&amp;nbsp;float(raw_item.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;raw_item.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;stock&amp;quot;]&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;num&amp;quot;,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;shop_name&amp;quot;]&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;detail_url&amp;quot;]&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;detail_url&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;sku规格简单提取
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list&amp;nbsp;=&amp;nbsp;raw_item.get(&amp;quot;skus&amp;quot;,&amp;nbsp;{}).get(&amp;quot;sku&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_info&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;sku_list:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_info.append({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_id&amp;quot;:&amp;nbsp;sku.get(&amp;quot;sku_id&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;spec_name&amp;quot;:&amp;nbsp;sku.get(&amp;quot;properties_name&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_price&amp;quot;:&amp;nbsp;sku.get(&amp;quot;price&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_stock&amp;quot;:&amp;nbsp;sku.get(&amp;quot;quantity&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out[&amp;quot;sku_info&amp;quot;]&amp;nbsp;=&amp;nbsp;json.dumps(sku_info,&amp;nbsp;ensure_ascii=False)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;out


def&amp;nbsp;save_to_csv(data_row:&amp;nbsp;dict):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;单条记录追加保存到csv历史文件&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new&amp;nbsp;=&amp;nbsp;pd.DataFrame([data_row])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;文件存在就追加，不存在新建
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;with&amp;nbsp;open(SAVE_FILE,&amp;nbsp;&amp;quot;r&amp;quot;,&amp;nbsp;encoding=&amp;quot;utf-8-sig&amp;quot;)&amp;nbsp;as&amp;nbsp;f:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new.to_csv(SAVE_FILE,&amp;nbsp;mode=&amp;quot;a&amp;quot;,&amp;nbsp;header=False,&amp;nbsp;index=False,&amp;nbsp;encoding=&amp;quot;utf-8-sig&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;FileNotFoundError:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df_new.to_csv(SAVE_FILE,&amp;nbsp;mode=&amp;quot;w&amp;quot;,&amp;nbsp;header=True,&amp;nbsp;index=False,&amp;nbsp;encoding=&amp;quot;utf-8-sig&amp;quot;)


last_status&amp;nbsp;=&amp;nbsp;{}&amp;nbsp;&amp;nbsp;#&amp;nbsp;保存上一轮采集状态，用于异动对比


def&amp;nbsp;monitor_task():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;一轮完整监控任务，循环所有商品&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n==========开始一轮监控&amp;nbsp;{datetime.now().strftime(&amp;#39;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;#39;)}&amp;nbsp;==========&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;global&amp;nbsp;last_status
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;gid&amp;nbsp;in&amp;nbsp;MONITOR_GOODS_ID:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_raw&amp;nbsp;=&amp;nbsp;get_goods_detail(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed&amp;nbsp;=&amp;nbsp;parse_item(item_raw)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;parsed:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_csv(parsed)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品【{parsed[&amp;#39;title&amp;#39;][0:25]}...】现价：{parsed[&amp;#39;price&amp;#39;]}&amp;nbsp;库存：{parsed[&amp;#39;stock&amp;#39;]}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;和上一次数据对比，检测异动
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;prev&amp;nbsp;=&amp;nbsp;last_status.get(gid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;prev:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;价格下跌
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;parsed[&amp;quot;price&amp;quot;]&amp;nbsp;&amp;lt;&amp;nbsp;prev[&amp;quot;price&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️&amp;nbsp;价格异动提醒！商品{gid}发生降价，上次{prev[&amp;#39;price&amp;#39;]}，当前{parsed[&amp;#39;price&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;库存变动
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;parsed[&amp;quot;stock&amp;quot;]&amp;nbsp;!=&amp;nbsp;prev[&amp;quot;stock&amp;quot;]:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️&amp;nbsp;库存变动提醒！商品{gid}库存由{prev[&amp;#39;stock&amp;#39;]}变更为{parsed[&amp;#39;stock&amp;#39;]}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;更新记录
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last_status[gid]&amp;nbsp;=&amp;nbsp;parsed
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(2)


def&amp;nbsp;analysis_history():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;简单数据分析示例，读取本地历史，打印每个商品价格区间&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_csv(SAVE_FILE,&amp;nbsp;encoding=&amp;quot;utf-8-sig&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n----------历史数据分析结果----------&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;goods_id&amp;nbsp;in&amp;nbsp;df[&amp;quot;goods_id&amp;quot;].unique():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sub&amp;nbsp;=&amp;nbsp;df[df[&amp;quot;goods_id&amp;quot;]&amp;nbsp;==&amp;nbsp;goods_id]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;=&amp;nbsp;sub.iloc[0][&amp;quot;title&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;min_p&amp;nbsp;=&amp;nbsp;sub[&amp;quot;price&amp;quot;].min()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;max_p&amp;nbsp;=&amp;nbsp;sub[&amp;quot;price&amp;quot;].max()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;商品：{title[:30]}...&amp;nbsp;历史最低:{min_p}&amp;nbsp;历史最高:{max_p}，共采集{len(sub)}条记录&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;暂无历史数据，等待采集完成&amp;quot;,&amp;nbsp;e)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;执行一次
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor_task()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analysis_history()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;设置定时循环执行
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.every(MONITOR_INTERVAL).seconds.do(monitor_task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n已启动定时监控，每&amp;nbsp;{MONITOR_INTERVAL/60}分钟执行一次，Ctrl+C终止程序&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;schedule.run_pending()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(1)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;代码讲解&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;get_goods_detail&lt;/strong&gt;：核心获取函数，传入商品编号，工具会返回完整的商品全部维度信息，我们不需要处理网页；同时做了网络超时、异常返回的捕获，防止程序直接崩溃。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;blockquote&gt;&lt;p&gt;cache 参数：设置&lt;code&gt;no&lt;/code&gt;获取实时最新价格库存；设置&lt;code&gt;yes&lt;/code&gt;会读取缓存，响应更快，适合非实时分析场景。&lt;/p&gt;&lt;/blockquote&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;parse_item&lt;/strong&gt;：原始返回字段比较多，这个函数做字段精简，挑选我们监控业务需要的：采集时间、商品标题、价格、原价、库存、店铺、SKU 规格，SKU 数组转字符串方便存入表格。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;save_to_csv&lt;/strong&gt;：每一轮采集结果都写入本地 CSV，相当于自建一份商品时间线数据库，后续可以用 Excel、Pandas 做趋势分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;monitor_task&lt;/strong&gt;：一轮监控主逻辑，遍历全部待监控商品；并且用&lt;code&gt;last_status&lt;/code&gt;保存上一轮状态，对比发现降价、库存变化，控制台输出告警提示。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;analysis_history&lt;/strong&gt;：读取本地保存的历史记录，统计每个商品历史最高价、最低价，简单数据分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;主程序部分：运行一次，然后开启定时任务，按设定周期自动轮询。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;常见问题踩坑说明&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据拿不到？&lt;/strong&gt;

	&lt;/p&gt;&lt;/li&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;确认密钥信息复制正确，不要有多余空格换行；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;确认商品编号是正确有效，下架的商品会返回对应错误提示；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;不要把监控间隔设置得太短，短时间大量请求会触发限制。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;价格和页面显示不一致&lt;/strong&gt; 部分大促优惠券、满减活动属于页面动态计算，拿到的是商品基础标价。多规格商品，不同规格各自有独立价格，需要看返回 SKU 列表。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据存储&lt;/strong&gt; 示例中用 CSV 文件存储，适合小批量监控；如果要监控几百上千个商品，可以改成 SQLite / MySQL 数据库存储。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警扩展&lt;/strong&gt; 当前示例只在控制台打印提醒，实际项目中可以拓展：邮件推送、企业微信、钉钉消息，实现真正的消息通知。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;拓展玩法&lt;/h2&gt;&lt;p&gt;拿到标准化结构化数据之后，可以衍生很多有意思的小项目：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;个人比价助手&lt;/strong&gt;：监控心仪家电、数码产品，降到心理价位就收到提醒；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;商家竞品分析&lt;/strong&gt;：批量跟踪同行多款竞品，记录调价节奏、库存变化；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据报表输出&lt;/strong&gt;：定时把历史数据生成 Excel 报表，做周度、月度价格统计；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;筛选过滤 SKU&lt;/strong&gt;：针对特定规格做监控，比如只盯某一个型号的价格变动。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;写在最后&lt;/h2&gt;&lt;p&gt;很多时候我们写脚本，大量时间消耗在网页页面适配、反爬对抗上。OpenClaw 这类工具，把网页解析这一层封装完成，直接输出干净的结构化数据。我们可以把重心放回业务逻辑：监控逻辑、数据存储、数据分析。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Fri, 09 Oct 2026 11:48:12 +0800</pubDate></item><item><title>告别手工盯屏，用 Open‑Claw 快速搭建电商商品监控与数据分析体系</title><link>https://www.shopapi.com.cn/?id=234</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;本文适合电商运营、数据爱好者、Python 开发，手把手教你实现商品价格、库存、规格自动跟踪，完成历史数据存储、简单可视化分析。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营、竞品调研的时候，很多人都有这样的痛点：需要持续关注多款商品的价格变动、库存变化、促销活动。如果每天手动打开网页复制记录，不仅耗费大量时间，还很容易漏掉转瞬即逝的降价、缺货节点。&lt;/p&gt;&lt;p&gt;直接写脚本解析网页源码，又会遇到重重阻碍：网页动态渲染、频繁触发访问限制、页面改版之后全部代码失效，调试成本极高。&lt;/p&gt;&lt;p&gt;最近接触到 Open‑Claw 这套工具套件，可以省去页面解析、反访问限制处理这些繁琐工作，直接拿到结构化的商品全量信息。我们只需要专注业务逻辑：定时拉取数据、落地存储、做统计分析，快速搭建一套属于自己的商品监控系统。&lt;/p&gt;&lt;p&gt;本文会完整演示从数据获取、持久化存储，到监控告警、数据分析可视化完整流程，所有示例均可直接修改配置运行。&lt;/p&gt;&lt;h2&gt;整体实现思路&lt;/h2&gt;&lt;p&gt;整套系统分成 4 个核心环节：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;9&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E6%95%B0%E6%8D%AE%E8%8E%B7%E5%8F%96%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22kL8oqx-1791424704660%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;数据获取&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;数据获取&quot;&gt;数据获取&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;：输入商品编号，拿到标题、现价、原价、库存、多规格信息、店铺信息等结构化内容。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;持久化存储&lt;/strong&gt;：把每一次采集的数据存入本地数据库，留存历史记录，用于后续对比。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;监控逻辑&lt;/strong&gt;：定时执行采集任务，对比本次和上一轮价格、库存，出现变动输出提醒。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据分析&lt;/strong&gt;：基于历史数据，做价格波动统计、规格对比，输出图表，辅助运营判断。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;环境准备：Python3.7 以上，安装依赖库&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22pip%20install%20requests%20pandas%20matplotlib%20sqlite3%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;matplotlib&amp;nbsp;sqlite3&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;一、商品原始数据获取&lt;/h2&gt;&lt;p&gt;首先实现基础读取函数，传入商品编号就可以拿到完整结构化资料。只需要配置好自己的凭证信息，不需要处理网页 DOM、JS 渲染等复杂逻辑。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20coding%3Autf%E2%80%918%5Cnimport%20requests%5Cnimport%20json%5Cnimport%20time%5Cnimport%20sqlite3%5Cnimport%20pandas%20as%20pd%5Cnimport%20matplotlib.pyplot%20as%20plt%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%20%E9%85%8D%E7%BD%AE%E5%8C%BA%EF%BC%8C%E4%BF%AE%E6%94%B9%E4%B8%BA%E8%87%AA%E5%B7%B1%E7%9A%84%E5%87%AD%E8%AF%81%E4%B8%8E%E7%9B%91%E6%8E%A7%E5%95%86%E5%93%81%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%5CnACCESS_KEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E8%AE%BF%E9%97%AE%E5%87%AD%E8%AF%81%5C%22%5CnACCESS_SECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%AE%89%E5%85%A8%E5%AF%86%E9%92%A5%5C%22%5CnMONITOR_GOODS_ID%20%3D%20%5C%2210335871600%5C%22%5CnMONITOR_CYCLE%20%3D%201800%20%20%23%20%E7%9B%91%E6%8E%A7%E5%91%A8%E6%9C%9F%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E7%A4%BA%E4%BE%8B30%E5%88%86%E9%92%9F%E9%87%87%E9%9B%86%E4%B8%80%E6%AC%A1%5Cn%23%20%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%3D%5Cn%5Cndef%20get_goods_detail(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E7%BB%93%E6%9E%84%E5%8C%96%E8%AF%A6%E6%83%85%5Cn%20%20%20%20%3Aparam%20goods_id%3A%20%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5Cn%20%20%20%20%3Areturn%3A%20dict%E5%95%86%E5%93%81%E6%95%B0%E6%8D%AE%EF%BC%8C%E5%A4%B1%E8%B4%A5%E8%BF%94%E5%9B%9ENone%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22Accept%E2%80%91Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20ACCESS_KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20ACCESS_SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22https%3A%2F%2Fapi%E2%80%91gw.onebound.cn%2Fjd%2Fitem_get_pro%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20params%3Dparams%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20headers%3Dheaders%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20timeout%3D20%5Cn%20%20%20%20%20%20%20%20)%5Cn%20%20%20%20%20%20%20%20res_json%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20%23%20%E5%88%A4%E6%96%AD%E8%BF%94%E5%9B%9E%E7%8A%B6%E6%80%81%5Cn%20%20%20%20%20%20%20%20if%20res_json.get(%5C%22error_code%5C%22)%20!%3D%20%5C%220000%5C%22%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%E8%8E%B7%E5%8F%96%E5%A4%B1%E8%B4%A5%EF%BC%9A%7Bres_json.get(&amp;#39;reason&amp;#39;%2C&amp;#39;%E6%9C%AA%E7%9F%A5%E9%94%99%E8%AF%AF&amp;#39;)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20item_data%20%3D%20res_json.get(%5C%22item%5C%22)%5Cn%20%20%20%20%20%20%20%20if%20not%20item_data%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(%5C%22%E6%9C%AA%E6%89%BE%E5%88%B0%E5%AF%B9%E5%BA%94%E5%95%86%E5%93%81%5C%22)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20%20%20%20%20return%20item_data%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E7%BD%91%E7%BB%9C%E5%BC%82%E5%B8%B8%3A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;coding:utf‑8
import&amp;nbsp;requests
import&amp;nbsp;json
import&amp;nbsp;time
import&amp;nbsp;sqlite3
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd
import&amp;nbsp;matplotlib.pyplot&amp;nbsp;as&amp;nbsp;plt
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

#&amp;nbsp;=========&amp;nbsp;配置区，修改为自己的凭证与监控商品&amp;nbsp;=========
ACCESS_KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的访问凭证&amp;quot;
ACCESS_SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的安全密钥&amp;quot;
MONITOR_GOODS_ID&amp;nbsp;=&amp;nbsp;&amp;quot;10335871600&amp;quot;
MONITOR_CYCLE&amp;nbsp;=&amp;nbsp;1800&amp;nbsp;&amp;nbsp;#&amp;nbsp;监控周期，单位秒，示例30分钟采集一次
#&amp;nbsp;==================================================

def&amp;nbsp;get_goods_detail(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;获取商品结构化详情
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:param&amp;nbsp;goods_id:&amp;nbsp;商品编号
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:return:&amp;nbsp;dict商品数据，失败返回None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept‑Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;ACCESS_KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;ACCESS_SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;https://api‑gw.onebound.cn/jd/item_get_pro&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params=params,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers=headers,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout=20
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;res_json&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;判断返回状态
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;res_json.get(&amp;quot;error_code&amp;quot;)&amp;nbsp;!=&amp;nbsp;&amp;quot;0000&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;获取失败：{res_json.get(&amp;#39;reason&amp;#39;,&amp;#39;未知错误&amp;#39;)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_data&amp;nbsp;=&amp;nbsp;res_json.get(&amp;quot;item&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;未找到对应商品&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;item_data
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;网络异常:{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;调用示例：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22if%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20info%20%3D%20get_goods_detail(MONITOR_GOODS_ID)%5Cn%20%20%20%20if%20info%3A%5Cn%20%20%20%20%20%20%20%20print(json.dumps(info%2C%20ensure_ascii%3DFalse%2C%20indent%3D2))%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;info&amp;nbsp;=&amp;nbsp;get_goods_detail(MONITOR_GOODS_ID)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(json.dumps(info,&amp;nbsp;ensure_ascii=False,&amp;nbsp;indent=2))&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;运行之后，即可拿到商品标题、当前售价、划线原价、库存、全部规格 sku、店铺名称、商品大图等完整结构化字典，不用自己写复杂页面解析逻辑。&lt;/p&gt;&lt;h2&gt;二、本地数据库存储历史数据&lt;/h2&gt;&lt;p&gt;要做监控和分析，必须留存每一次采集快照。这里选用轻量的 SQLite，无需额外安装数据库服务。新建数据表保存每次采集时间、商品基础信息。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20init_db()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E6%8D%AE%E5%BA%93%EF%BC%8C%E5%88%9B%E5%BB%BA%E5%95%86%E5%93%81%E7%9B%91%E6%8E%A7%E8%AE%B0%E5%BD%95%E8%A1%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20cursor%20%3D%20conn.cursor()%5Cn%20%20%20%20create_sql%20%3D%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20goods_record%20(%5Cn%20%20%20%20%20%20%20%20id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%2C%5Cn%20%20%20%20%20%20%20%20goods_id%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20title%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20original_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20stock%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20shop_name%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20capture_time%20TEXT%5Cn%20%20%20%20)%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20cursor.execute(create_sql)%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%5Cndef%20save_to_db(item)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%8D%95%E6%9D%A1%E5%95%86%E5%93%81%E8%AE%B0%E5%BD%95%E5%85%A5%E5%BA%93%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20cursor%20%3D%20conn.cursor()%5Cn%20%20%20%20insert_sql%20%3D%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20INSERT%20INTO%20goods_record%5Cn%20%20%20%20(goods_id%2C%20title%2C%20price%2C%20original_price%2C%20stock%2C%20shop_name%2C%20capture_time)%5Cn%20%20%20%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5Cn%20%20%20%20%5C%22%5C%22%5C%22%5Cn%20%20%20%20cursor.execute(insert_sql%2C%20(%5Cn%20%20%20%20%20%20%20%20item.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20item.get(%5C%22title%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20float(item.get(%5C%22price%5C%22%2C0))%2C%5Cn%20%20%20%20%20%20%20%20float(item.get(%5C%22orginal_price%5C%22%2C0))%2C%5Cn%20%20%20%20%20%20%20%20int(item.get(%5C%22num%5C%22%2C0))%2C%5Cn%20%20%20%20%20%20%20%20item.get(%5C%22seller_info%5C%22%2C%7B%7D).get(%5C%22shop_name%5C%22%2C%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20datetime.now().strftime(%5C%22%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S%5C%22)%5Cn%20%20%20%20))%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;init_db():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;初始化数据库，创建商品监控记录表&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;create_sql&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;goods_record&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;AUTOINCREMENT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;original_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shop_name&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capture_time&amp;nbsp;TEXT
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(create_sql)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()

def&amp;nbsp;save_to_db(item):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;单条商品记录入库&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;insert_sql&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_record
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(goods_id,&amp;nbsp;title,&amp;nbsp;price,&amp;nbsp;original_price,&amp;nbsp;stock,&amp;nbsp;shop_name,&amp;nbsp;capture_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?,?)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(insert_sql,&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;title&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float(item.get(&amp;quot;price&amp;quot;,0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float(item.get(&amp;quot;orginal_price&amp;quot;,0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int(item.get(&amp;quot;num&amp;quot;,0)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;seller_info&amp;quot;,{}).get(&amp;quot;shop_name&amp;quot;,&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;datetime.now().strftime(&amp;quot;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;初始化数据库：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;4&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22init_db()%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;init_db()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;三、实现监控告警逻辑&lt;/h2&gt;&lt;p&gt;核心逻辑：每次拿到最新数据，读取数据库中上一条记录，对比价格、库存，如果发生变化打印告警提示，你可以扩展对接企业微信、邮件实现消息推送。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;5&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20get_last_record(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%AF%BB%E5%8F%96%E8%AF%A5%E5%95%86%E5%93%81%E6%9C%80%E8%BF%91%E4%B8%80%E6%9D%A1%E5%8E%86%E5%8F%B2%E8%AE%B0%E5%BD%95%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20df%20%3D%20pd.read_sql(%5Cn%20%20%20%20%20%20%20%20f%5C%22SELECT%20*%20FROM%20goods_record%20WHERE%20goods_id%3D&amp;#39;%7Bgoods_id%7D&amp;#39;%20ORDER%20BY%20id%20DESC%20LIMIT%201%5C%22%2C%5Cn%20%20%20%20%20%20%20%20conn%5Cn%20%20%20%20)%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20if%20len(df)%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20return%20df.iloc%5B0%5D.to_dict()%5Cn%20%20%20%20return%20None%5Cn%5Cndef%20check_change(new_item)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%AF%B9%E6%AF%94%E6%96%B0%E6%97%A7%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%A3%80%E6%B5%8B%E5%8F%98%E5%8A%A8%EF%BC%8C%E8%BE%93%E5%87%BA%E5%91%8A%E8%AD%A6%5C%22%5C%22%5C%22%5Cn%20%20%20%20goods_id%20%3D%20new_item.get(%5C%22num_iid%5C%22)%5Cn%20%20%20%20last%20%3D%20get_last_record(goods_id)%5Cn%20%20%20%20if%20not%20last%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E9%A6%96%E6%AC%A1%E9%87%87%E9%9B%86%EF%BC%8C%E6%97%A0%E5%8E%86%E5%8F%B2%E5%AF%B9%E6%AF%94%E6%95%B0%E6%8D%AE%5C%22)%5Cn%20%20%20%20%20%20%20%20return%5Cn%5Cn%20%20%20%20new_price%20%3D%20float(new_item.get(%5C%22price%5C%22%2C0))%5Cn%20%20%20%20old_price%20%3D%20last%5B%5C%22price%5C%22%5D%5Cn%20%20%20%20new_stock%20%3D%20int(new_item.get(%5C%22num%5C%22%2C0))%5Cn%20%20%20%20old_stock%20%3D%20last%5B%5C%22stock%5C%22%5D%5Cn%5Cn%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%3D%E5%8F%98%E5%8A%A8%E6%A3%80%E6%B5%8B%E3%80%90%7Bnew_item.get(&amp;#39;title&amp;#39;)%7D%E3%80%91%3D%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20if%20new_price%20!%3D%20old_price%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E4%BB%B7%E6%A0%BC%E5%8F%98%E5%8A%A8%EF%BC%81%E6%97%A7%E4%BB%B7%EF%BC%9A%7Bold_price%7D%20%E2%86%92%20%E6%96%B0%E4%BB%B7%EF%BC%9A%7Bnew_price%7D%5C%22)%5Cn%20%20%20%20if%20new_stock%20!%3D%20old_stock%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E5%BA%93%E5%AD%98%E5%8F%98%E5%8A%A8%EF%BC%81%E6%97%A7%E5%BA%93%E5%AD%98%EF%BC%9A%7Bold_stock%7D%20%E2%86%92%20%E6%96%B0%E5%BA%93%E5%AD%98%EF%BC%9A%7Bnew_stock%7D%5C%22)%5Cn%20%20%20%20if%20new_price%20%3D%3D%20old_price%20and%20new_stock%20%3D%3D%20old_stock%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E2%9C%85%E4%BB%B7%E6%A0%BC%E5%BA%93%E5%AD%98%E5%9D%87%E6%97%A0%E5%8F%98%E5%8C%96%5C%22)%5Cn%5Cndef%20monitor_loop()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%BE%AA%E7%8E%AF%E7%9B%91%E6%8E%A7%E4%B8%BB%E9%80%BB%E8%BE%91%5C%22%5C%22%5C%22%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%5C%5Cn%3D%3D%3D%3D%20%7Bdatetime.now().strftime(&amp;#39;%25Y%E2%80%91%25m%E2%80%91%25d%20%25H%3A%25M%3A%25S&amp;#39;)%7D%20%E6%89%A7%E8%A1%8C%E9%87%87%E9%9B%86%20%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20%20%20%20%20item_info%20%3D%20get_goods_detail(MONITOR_GOODS_ID)%5Cn%20%20%20%20%20%20%20%20if%20item_info%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20check_change(item_info)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20save_to_db(item_info)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E7%AD%89%E5%BE%85%7BMONITOR_CYCLE%2F60%7D%E5%88%86%E9%92%9F%EF%BC%8C%E8%BF%9B%E5%85%A5%E4%B8%8B%E4%B8%80%E8%BD%AE%E9%87%87%E9%9B%86...%5C%22)%5Cn%20%20%20%20%20%20%20%20time.sleep(MONITOR_CYCLE)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;get_last_record(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;读取该商品最近一条历史记录&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_sql(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;goods_record&amp;nbsp;WHERE&amp;nbsp;goods_id=&amp;#39;{goods_id}&amp;#39;&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;id&amp;nbsp;DESC&amp;nbsp;LIMIT&amp;nbsp;1&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(df)&amp;nbsp;&amp;gt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;df.iloc[0].to_dict()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

def&amp;nbsp;check_change(new_item):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;对比新旧数据，检测变动，输出告警&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_id&amp;nbsp;=&amp;nbsp;new_item.get(&amp;quot;num_iid&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;last&amp;nbsp;=&amp;nbsp;get_last_record(goods_id)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;last:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;首次采集，无历史对比数据&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new_price&amp;nbsp;=&amp;nbsp;float(new_item.get(&amp;quot;price&amp;quot;,0))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;old_price&amp;nbsp;=&amp;nbsp;last[&amp;quot;price&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;new_stock&amp;nbsp;=&amp;nbsp;int(new_item.get(&amp;quot;num&amp;quot;,0))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;old_stock&amp;nbsp;=&amp;nbsp;last[&amp;quot;stock&amp;quot;]

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n=====变动检测【{new_item.get(&amp;#39;title&amp;#39;)}】=====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;new_price&amp;nbsp;!=&amp;nbsp;old_price:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️价格变动！旧价：{old_price}&amp;nbsp;→&amp;nbsp;新价：{new_price}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;new_stock&amp;nbsp;!=&amp;nbsp;old_stock:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️库存变动！旧库存：{old_stock}&amp;nbsp;→&amp;nbsp;新库存：{new_stock}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;new_price&amp;nbsp;==&amp;nbsp;old_price&amp;nbsp;and&amp;nbsp;new_stock&amp;nbsp;==&amp;nbsp;old_stock:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;✅价格库存均无变化&amp;quot;)

def&amp;nbsp;monitor_loop():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;循环监控主逻辑&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;\n====&amp;nbsp;{datetime.now().strftime(&amp;#39;%Y‑%m‑%d&amp;nbsp;%H:%M:%S&amp;#39;)}&amp;nbsp;执行采集&amp;nbsp;====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item_info&amp;nbsp;=&amp;nbsp;get_goods_detail(MONITOR_GOODS_ID)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;item_info:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;check_change(item_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_db(item_info)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;等待{MONITOR_CYCLE/60}分钟，进入下一轮采集...&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(MONITOR_CYCLE)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;启动监控循环：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;6&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20%E5%90%AF%E5%8A%A8%E6%8C%81%E7%BB%AD%E7%9B%91%E6%8E%A7%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20init_db()%5Cn%20%20%20%20monitor_loop()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;启动持续监控
if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_db()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor_loop()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;p&gt;提示：实际部署可以使用 nohup 或者 systemd 把脚本放后台运行，实现 7*24 小时不间断盯商品。当检测到降价、库存清空，控制台直接输出提醒，业务场景可以继续扩展消息推送。&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;h2&gt;四、基于历史记录做数据分析与可视化&lt;/h2&gt;&lt;p&gt;积累一段时间历史数据后，就可以做数据分析，观察商品价格波动趋势，判断促销节点。下面从数据库读取全部历史记录，绘制价格走势图表。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;7&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20analysis_and_plot(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%AF%BB%E5%8F%96%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%EF%BC%8C%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%2B%E7%BB%98%E5%88%B6%E4%BB%B7%E6%A0%BC%E8%B5%B0%E5%8A%BF%E5%9B%BE%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20df%20%3D%20pd.read_sql(%5Cn%20%20%20%20%20%20%20%20f%5C%22SELECT%20*%20FROM%20goods_record%20WHERE%20goods_id%3D&amp;#39;%7Bgoods_id%7D&amp;#39;%20ORDER%20BY%20capture_time%5C%22%2C%5Cn%20%20%20%20%20%20%20%20conn%5Cn%20%20%20%20)%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20if%20len(df)%20%3D%3D%200%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E6%9A%82%E6%97%A0%E5%8E%86%E5%8F%B2%E7%9B%91%E6%8E%A7%E6%95%B0%E6%8D%AE%5C%22)%5Cn%20%20%20%20%20%20%20%20return%5Cn%5Cn%20%20%20%20%23%20%E5%9F%BA%E7%A1%80%E7%BB%9F%E8%AE%A1%5Cn%20%20%20%20print(%5C%22%5C%5Cn%3D%3D%3D%3D%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E7%BB%9F%E8%AE%A1%E6%91%98%E8%A6%81%3D%3D%3D%3D%5C%22)%5Cn%20%20%20%20print(f%5C%22%E6%80%BB%E9%87%87%E9%9B%86%E8%AE%B0%E5%BD%95%E6%95%B0%EF%BC%9A%7Blen(df)%7D%5C%22)%5Cn%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E6%9C%80%E4%BD%8E%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bdf%5B&amp;#39;price&amp;#39;%5D.min()%7D%5C%22)%5Cn%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E6%9C%80%E9%AB%98%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bdf%5B&amp;#39;price&amp;#39;%5D.max()%7D%5C%22)%5Cn%20%20%20%20print(f%5C%22%E5%B9%B3%E5%9D%87%E5%94%AE%E4%BB%B7%EF%BC%9A%7Bround(df%5B&amp;#39;price&amp;#39;%5D.mean()%2C2)%7D%5C%22)%5Cn%5Cn%20%20%20%20%23%20%E7%BB%98%E5%9B%BE%E8%AE%BE%E7%BD%AE%E4%B8%AD%E6%96%87%5Cn%20%20%20%20plt.rcParams%5B%5C%22font.sans%E2%80%91serif%5C%22%5D%20%3D%20%5B%5C%22SimHei%5C%22%5D%5Cn%20%20%20%20plt.rcParams%5B%5C%22axes.unicode_minus%5C%22%5D%20%3D%20False%5Cn%5Cn%20%20%20%20df%5B%5C%22capture_time%5C%22%5D%20%3D%20pd.to_datetime(df%5B%5C%22capture_time%5C%22%5D)%5Cn%20%20%20%20plt.figure(figsize%3D(12%2C6))%5Cn%20%20%20%20plt.plot(df%5B%5C%22capture_time%5C%22%5D%2C%20df%5B%5C%22price%5C%22%5D%2C%20marker%3D%5C%22o%5C%22%2C%20label%3D%5C%22%E5%AE%9E%E6%97%B6%E5%94%AE%E4%BB%B7%5C%22)%5Cn%20%20%20%20plt.plot(df%5B%5C%22capture_time%5C%22%5D%2C%20df%5B%5C%22original_price%5C%22%5D%2C%20linestyle%3D%5C%22%E2%80%91%E2%80%91%5C%22%2C%20label%3D%5C%22%E5%8E%9F%E4%BB%B7%5C%22)%5Cn%20%20%20%20plt.title(%5C%22%E5%95%86%E5%93%81%E4%BB%B7%E6%A0%BC%E5%8E%86%E5%8F%B2%E7%9B%91%E6%8E%A7%E8%B5%B0%E5%8A%BF%5C%22)%5Cn%20%20%20%20plt.xlabel(%5C%22%E9%87%87%E9%9B%86%E6%97%B6%E9%97%B4%5C%22)%5Cn%20%20%20%20plt.ylabel(%5C%22%E4%BB%B7%E6%A0%BC%5C%22)%5Cn%20%20%20%20plt.legend()%5Cn%20%20%20%20plt.xticks(rotation%3D30)%5Cn%20%20%20%20plt.tight_layout()%5Cn%20%20%20%20plt.savefig(%5C%22price_trend.png%5C%22%2C%20dpi%3D150)%5Cn%20%20%20%20print(%5C%22%E5%9B%BE%E8%A1%A8%E5%B7%B2%E4%BF%9D%E5%AD%98%20price_trend.png%5C%22)%5Cn%20%20%20%20plt.show()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;analysis_and_plot(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;读取历史数据，数据分析+绘制价格走势图&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_sql(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;f&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;goods_record&amp;nbsp;WHERE&amp;nbsp;goods_id=&amp;#39;{goods_id}&amp;#39;&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;capture_time&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(df)&amp;nbsp;==&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;暂无历史监控数据&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;基础统计
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;\n====历史数据统计摘要====&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;总采集记录数：{len(df)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史最低售价：{df[&amp;#39;price&amp;#39;].min()}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史最高售价：{df[&amp;#39;price&amp;#39;].max()}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;平均售价：{round(df[&amp;#39;price&amp;#39;].mean(),2)}&amp;quot;)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;绘图设置中文
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.rcParams[&amp;quot;font.sans‑serif&amp;quot;]&amp;nbsp;=&amp;nbsp;[&amp;quot;SimHei&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.rcParams[&amp;quot;axes.unicode_minus&amp;quot;]&amp;nbsp;=&amp;nbsp;False

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df[&amp;quot;capture_time&amp;quot;]&amp;nbsp;=&amp;nbsp;pd.to_datetime(df[&amp;quot;capture_time&amp;quot;])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.figure(figsize=(12,6))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.plot(df[&amp;quot;capture_time&amp;quot;],&amp;nbsp;df[&amp;quot;price&amp;quot;],&amp;nbsp;marker=&amp;quot;o&amp;quot;,&amp;nbsp;label=&amp;quot;实时售价&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.plot(df[&amp;quot;capture_time&amp;quot;],&amp;nbsp;df[&amp;quot;original_price&amp;quot;],&amp;nbsp;linestyle=&amp;quot;‑‑&amp;quot;,&amp;nbsp;label=&amp;quot;原价&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.title(&amp;quot;商品价格历史监控走势&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.xlabel(&amp;quot;采集时间&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.ylabel(&amp;quot;价格&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.legend()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.xticks(rotation=30)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.tight_layout()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.savefig(&amp;quot;price_trend.png&amp;quot;,&amp;nbsp;dpi=150)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;图表已保存&amp;nbsp;price_trend.png&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;plt.show()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;调用分析函数：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;8&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22if%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20analysis_and_plot(MONITOR_GOODS_ID)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;analysis_and_plot(MONITOR_GOODS_ID)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;运行完成后，会输出统计信息，同时在本地生成一张价格趋势图片。运营可以通过这张图看什么时候降价、什么时候恢复原价，辅助判断采购、上架时机。&lt;/p&gt;&lt;h2&gt;五、扩展方向&lt;/h2&gt;&lt;p&gt;上面的例子只监控单个商品，还可以继续扩展能力：&lt;/p&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;批量监控&lt;/strong&gt;：维护一份商品编号列表，循环遍历批量获取多款竞品数据，一次性入库。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;SKU 维度监控&lt;/strong&gt;：解析返回内容中的规格列表，对每一个不同规格单独记录价格库存，监控不同配置的价差。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;消息推送&lt;/strong&gt;：检测到变动时调用企业微信 / 钉钉机器人接口，手机实时接收提醒，不用守着控制台。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;导出报表&lt;/strong&gt;：用 pandas 把数据库记录导出 csv，直接给到运营同事做周报分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异常容错&lt;/strong&gt;：增加重试逻辑，网络波动自动重试，避免单次网络问题中断整个监控任务。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;踩坑经验总结&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;不要把采集周期设置过短，过于频繁请求容易触发限流，业务场景一般 15‑60 分钟一次足够。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;拿到的数据里面，部分规格价格存在偏差，业务做决策建议多轮采集交叉核对，不要只看单次快照。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;商品下架、编号失效的时候会返回空结果，代码要做好判空，防止程序直接崩溃。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;如果长期后台运行，需要注意磁盘占用，定期清理过久的历史快照，或者做分表。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;写在最后&lt;/h2&gt;&lt;p&gt;很多人做电商数据相关开发，大部分时间消耗在页面解析、处理各类访问限制。Open‑Claw 把这些底层脏活全部封装起来，开发者可以把精力全部放在业务逻辑上。&lt;/p&gt;&lt;p&gt;十几行代码完成基础读取，再搭配上 Python 的数据存储、绘图能力，快速拥有一套自用的商品监控分析工具。不管是个人玩家做薅羊毛盯价，还是中小企业做竞品跟踪，这套思路都可以直接复用。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Thu, 08 Oct 2026 10:03:18 +0800</pubDate></item><item><title>告别手工复制粘贴：搭建电商商品自动化监控与数据分析实践</title><link>https://www.shopapi.com.cn/?id=233</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;适用场景：竞品盯价、库存巡检、商品信息跟踪、运营数据分析；语言 Python；适合电商运营、数据分析、后端开发阅读&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;前言&lt;/h2&gt;&lt;p&gt;做电商运营或者竞品分析，很多人还在重复一件枯燥工作：打开商品页面，复制标题、现价、原价、库存、规格信息，粘贴到 Excel 表格，每天定时手动记录一遍。&lt;/p&gt;&lt;p&gt;商品一旦多起来，几十个上百个商品，人工处理不仅耗时巨大，还很容易出错，价格变动、库存清零、规格更新常常错过最佳的处理时机。促销大促期间，商品信息变化飞快，依靠人工盯守几乎不可能做到实时跟进。&lt;/p&gt;&lt;p&gt;最近接触到 OpenClaw 这套工具，它可以把商品页面的各类信息直接转为结构化数据，省去复杂页面解析、反爬处理，我们只需要聚焦业务逻辑，快速搭建一套属于自己的数据监控脚本，实现定时采集、本地存储、简单统计分析，把重复工作交给程序完成。&lt;/p&gt;&lt;p&gt;本文从实际落地角度完整演示，从环境准备、编写采集脚本、数据持久化存储，再到简单数据分析，完整跑通一套可用方案。&lt;/p&gt;&lt;h2&gt;一、工具与环境准备&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Python3.7 及以上版本&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;依赖库安装&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22pip%20install%20requests%20pandas%20sqlite3%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;pip&amp;nbsp;install&amp;nbsp;requests&amp;nbsp;pandas&amp;nbsp;sqlite3&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;requests：负责网络请求获取结构化商品数据&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;pandas：做数据清洗、统计、导出报表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;sqlite3：轻量数据库，用来存放历史监控记录，无需额外安装服务&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;拿到 OpenClaw 凭证信息，包含&lt;strong&gt;&lt;strong&gt;&lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;6&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22%E4%B8%A4%E7%BB%84%E5%AF%86%E9%92%A5%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%22l8kfFW-1790563722241%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;两组密钥&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;两组密钥&quot;&gt;两组密钥&lt;/a&gt;&lt;/span&gt;&lt;/strong&gt;&lt;/strong&gt;，用于身份校验，替换脚本内对应位置即可。&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;提示：不要把密钥硬编码直接提交到公开代码仓库，生产环境建议放到环境变量中。&lt;/p&gt;&lt;/blockquote&gt;&lt;h2&gt;二、核心采集脚本编写&lt;/h2&gt;&lt;p&gt;我们的目标是拿到商品：商品编号、标题、现价、原价、库存、规格 SKU 列表、商品链接、图片地址、店铺名称等关键业务字段，做结构化解析。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20-*-%20coding%3Autf-8%20-*-%5Cnimport%20requests%5Cnimport%20time%5Cnimport%20json%5Cnimport%20sqlite3%5Cnimport%20pandas%20as%20pd%5Cn%5Cn%23%20--------------------------%E9%85%8D%E7%BD%AE%E5%8C%BA%E5%9F%9F%EF%BC%8C%E8%AF%B7%E4%BF%AE%E6%94%B9--------------------------%5CnKEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%87%AD%E8%AF%81key%5C%22%5CnSECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84%E5%87%AD%E8%AF%81secret%5C%22%5CnTARGET_GOODS_ID%20%3D%20%5C%2210335871600%5C%22%20%20%23%20%E9%9C%80%E8%A6%81%E7%9B%91%E6%8E%A7%E7%9A%84%E5%95%86%E5%93%81%E7%BC%96%E5%8F%B7%5CnMONITOR_INTERVAL%20%3D%201800%20%20%23%20%E7%9B%91%E6%8E%A7%E5%91%A8%E6%9C%9F%EF%BC%8C%E5%8D%95%E4%BD%8D%E7%A7%92%EF%BC%8C%E8%BF%99%E9%87%8C%E8%AE%BE%E7%BD%AE30%E5%88%86%E9%92%9F%E9%87%87%E9%9B%86%E4%B8%80%E6%AC%A1%5Cn%23%20----------------------------------------------------------------%5Cn%5Cndef%20fetch_goods_data(goods_id)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E7%BB%93%E6%9E%84%E5%8C%96%E5%8E%9F%E5%A7%8B%E6%95%B0%E6%8D%AE%5C%22%5C%22%5C%22%5Cn%20%20%20%20base_url%20%3D%20%5C%22https%3A%2F%2Fapi-gw.onebound.cn%2Fjd%2Fitem_get_pro%5C%22%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20goods_id%5Cn%20%20%20%20%7D%5Cn%20%20%20%20headers%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22Accept-Encoding%5C%22%3A%20%5C%22gzip%5C%22%2C%5Cn%20%20%20%20%20%20%20%20%5C%22Connection%5C%22%3A%20%5C%22close%5C%22%5Cn%20%20%20%20%7D%5Cn%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20resp%20%3D%20requests.get(base_url%2C%20params%3Dparams%2C%20headers%3Dheaders%2C%20timeout%3D20)%5Cn%20%20%20%20%20%20%20%20resp.raise_for_status()%5Cn%20%20%20%20%20%20%20%20result%20%3D%20resp.json()%5Cn%20%20%20%20%20%20%20%20return%20result%5Cn%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E8%8E%B7%E5%8F%96%E5%95%86%E5%93%81%E6%95%B0%E6%8D%AE%E5%BC%82%E5%B8%B8%EF%BC%9A%7Bstr(e)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%5Cndef%20parse_goods_info(raw_json)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E8%A7%A3%E6%9E%90%E5%8E%9F%E5%A7%8B%E8%BF%94%E5%9B%9E%E5%86%85%E5%AE%B9%EF%BC%8C%E6%8F%90%E5%8F%96%E4%B8%9A%E5%8A%A1%E9%9C%80%E8%A6%81%E5%AD%97%E6%AE%B5%5C%22%5C%22%5C%22%5Cn%20%20%20%20if%20not%20raw_json%20or%20%5C%22item%5C%22%20not%20in%20raw_json%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%20%20%20%20item%20%3D%20raw_json.get(%5C%22item%5C%22%2C%20%7B%7D)%5Cn%20%20%20%20if%20not%20item%3A%5Cn%20%20%20%20%20%20%20%20return%20None%5Cn%5Cn%20%20%20%20data%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20item.get(%5C%22num_iid%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22title%5C%22%3A%20item.get(%5C%22title%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22price%5C%22%3A%20float(item.get(%5C%22price%5C%22%2C%200))%20if%20item.get(%5C%22price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%5C%22orginal_price%5C%22%3A%20float(item.get(%5C%22orginal_price%5C%22%2C0))%20if%20item.get(%5C%22orginal_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%5C%22stock%5C%22%3A%20item.get(%5C%22num%5C%22%2C%200)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22detail_url%5C%22%3A%20item.get(%5C%22detail_url%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22pic_url%5C%22%3A%20item.get(%5C%22pic_url%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22brand%5C%22%3A%20item.get(%5C%22brand%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22shop_name%5C%22%3A%20item.get(%5C%22seller_info%5C%22%2C%20%7B%7D).get(%5C%22shop_name%5C%22%2C%20%5C%22%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%5C%22capture_time%5C%22%3A%20time.strftime(%5C%22%25Y-%25m-%25d%20%25H%3A%25M%3A%25S%5C%22%2C%20time.localtime())%5Cn%20%20%20%20%7D%5Cn%5Cn%20%20%20%20%23%20%E6%8F%90%E5%8F%96%E5%A4%9A%E8%A7%84%E6%A0%BCSKU%E4%BF%A1%E6%81%AF%5Cn%20%20%20%20sku_list%20%3D%20%5B%5D%5Cn%20%20%20%20skus_data%20%3D%20item.get(%5C%22skus%5C%22%2C%20%7B%7D).get(%5C%22sku%5C%22%2C%20%5B%5D)%5Cn%20%20%20%20for%20sku%20in%20skus_data%3A%5Cn%20%20%20%20%20%20%20%20sku_item%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_id%5C%22%3A%20sku.get(%5C%22sku_id%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_price%5C%22%3A%20sku.get(%5C%22price%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_original_price%5C%22%3A%20sku.get(%5C%22orginal_price%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_properties%5C%22%3A%20sku.get(%5C%22properties_name%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20%5C%22sku_stock%5C%22%3A%20sku.get(%5C%22quantity%5C%22)%5Cn%20%20%20%20%20%20%20%20%7D%5Cn%20%20%20%20%20%20%20%20sku_list.append(sku_item)%5Cn%20%20%20%20data%5B%5C%22sku_info%5C%22%5D%20%3D%20sku_list%5Cn%20%20%20%20return%20data%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;-*-&amp;nbsp;coding:utf-8&amp;nbsp;-*-
import&amp;nbsp;requests
import&amp;nbsp;time
import&amp;nbsp;json
import&amp;nbsp;sqlite3
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd

#&amp;nbsp;--------------------------配置区域，请修改--------------------------
KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的凭证key&amp;quot;
SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的凭证secret&amp;quot;
TARGET_GOODS_ID&amp;nbsp;=&amp;nbsp;&amp;quot;10335871600&amp;quot;&amp;nbsp;&amp;nbsp;#&amp;nbsp;需要监控的商品编号
MONITOR_INTERVAL&amp;nbsp;=&amp;nbsp;1800&amp;nbsp;&amp;nbsp;#&amp;nbsp;监控周期，单位秒，这里设置30分钟采集一次
#&amp;nbsp;----------------------------------------------------------------

def&amp;nbsp;fetch_goods_data(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;获取商品结构化原始数据&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;base_url&amp;nbsp;=&amp;nbsp;&amp;quot;https://api-gw.onebound.cn/jd/item_get_pro&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;goods_id
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;headers&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Accept-Encoding&amp;quot;:&amp;nbsp;&amp;quot;gzip&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;Connection&amp;quot;:&amp;nbsp;&amp;quot;close&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(base_url,&amp;nbsp;params=params,&amp;nbsp;headers=headers,&amp;nbsp;timeout=20)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;result&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;result
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;获取商品数据异常：{str(e)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None


def&amp;nbsp;parse_goods_info(raw_json):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;解析原始返回内容，提取业务需要字段&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;raw_json&amp;nbsp;or&amp;nbsp;&amp;quot;item&amp;quot;&amp;nbsp;not&amp;nbsp;in&amp;nbsp;raw_json:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item&amp;nbsp;=&amp;nbsp;raw_json.get(&amp;quot;item&amp;quot;,&amp;nbsp;{})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;item:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;None

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;item.get(&amp;quot;num_iid&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;title&amp;quot;:&amp;nbsp;item.get(&amp;quot;title&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;price&amp;quot;:&amp;nbsp;float(item.get(&amp;quot;price&amp;quot;,&amp;nbsp;0))&amp;nbsp;if&amp;nbsp;item.get(&amp;quot;price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;orginal_price&amp;quot;:&amp;nbsp;float(item.get(&amp;quot;orginal_price&amp;quot;,0))&amp;nbsp;if&amp;nbsp;item.get(&amp;quot;orginal_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;stock&amp;quot;:&amp;nbsp;item.get(&amp;quot;num&amp;quot;,&amp;nbsp;0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;detail_url&amp;quot;:&amp;nbsp;item.get(&amp;quot;detail_url&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;pic_url&amp;quot;:&amp;nbsp;item.get(&amp;quot;pic_url&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;brand&amp;quot;:&amp;nbsp;item.get(&amp;quot;brand&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;shop_name&amp;quot;:&amp;nbsp;item.get(&amp;quot;seller_info&amp;quot;,&amp;nbsp;{}).get(&amp;quot;shop_name&amp;quot;,&amp;nbsp;&amp;quot;&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;capture_time&amp;quot;:&amp;nbsp;time.strftime(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;,&amp;nbsp;time.localtime())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;提取多规格SKU信息
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list&amp;nbsp;=&amp;nbsp;[]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;skus_data&amp;nbsp;=&amp;nbsp;item.get(&amp;quot;skus&amp;quot;,&amp;nbsp;{}).get(&amp;quot;sku&amp;quot;,&amp;nbsp;[])
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;skus_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_item&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_id&amp;quot;:&amp;nbsp;sku.get(&amp;quot;sku_id&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_price&amp;quot;:&amp;nbsp;sku.get(&amp;quot;price&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_original_price&amp;quot;:&amp;nbsp;sku.get(&amp;quot;orginal_price&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_properties&amp;quot;:&amp;nbsp;sku.get(&amp;quot;properties_name&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;sku_stock&amp;quot;:&amp;nbsp;sku.get(&amp;quot;quantity&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_list.append(sku_item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data[&amp;quot;sku_info&amp;quot;]&amp;nbsp;=&amp;nbsp;sku_list
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;data&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;三、本地数据库存储：保存历史监控记录&lt;/h2&gt;&lt;p&gt;想要做价格趋势、库存变化分析，必须把每一次采集结果落地保存，这里选用 sqlite，开箱即用。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20init_db()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%88%9D%E5%A7%8B%E5%8C%96%E6%95%B0%E6%8D%AE%E5%BA%93%E8%A1%A8%5C%22%5C%22%5C%22%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20cursor%20%3D%20conn.cursor()%5Cn%20%20%20%20%23%20%E5%95%86%E5%93%81%E4%B8%BB%E8%AE%B0%E5%BD%95%E8%A1%A8%5Cn%20%20%20%20cursor.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20goods_record%20(%5Cn%20%20%20%20%20%20%20%20id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%2C%5Cn%20%20%20%20%20%20%20%20num_iid%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20title%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20orginal_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20stock%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20brand%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20shop_name%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20capture_time%20TEXT%5Cn%20%20%20%20)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;)%5Cn%20%20%20%20%23%20SKU%E8%A7%84%E6%A0%BC%E5%AD%90%E8%A1%A8%EF%BC%8C%E5%AD%98%E5%82%A8%E5%A4%9A%E8%A7%84%E6%A0%BC%E6%95%B0%E6%8D%AE%5Cn%20%20%20%20cursor.execute(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20goods_sku%20(%5Cn%20%20%20%20%20%20%20%20id%20INTEGER%20PRIMARY%20KEY%20AUTOINCREMENT%2C%5Cn%20%20%20%20%20%20%20%20goods_record_id%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20sku_id%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20sku_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20sku_original_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20sku_properties%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20sku_stock%20INTEGER%5Cn%20%20%20%20)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;)%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%5Cn%5Cndef%20save_to_db(parsed_data)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E5%B0%86%E8%A7%A3%E6%9E%90%E5%90%8E%E7%9A%84%E6%95%B0%E6%8D%AE%E5%86%99%E5%85%A5%E6%95%B0%E6%8D%AE%E5%BA%93%5C%22%5C%22%5C%22%5Cn%20%20%20%20if%20not%20parsed_data%3A%5Cn%20%20%20%20%20%20%20%20return%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20cursor%20%3D%20conn.cursor()%5Cn%20%20%20%20%23%20%E5%86%99%E5%85%A5%E4%B8%BB%E8%A1%A8%5Cn%20%20%20%20insert_sql%20%3D%20&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20INSERT%20INTO%20goods_record(num_iid%2C%20title%2C%20price%2C%20orginal_price%2C%20stock%2C%20brand%2C%20shop_name%2C%20capture_time)%5Cn%20%20%20%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20cursor.execute(insert_sql%2C%20(%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22num_iid%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22title%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22orginal_price%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22stock%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22brand%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22shop_name%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20parsed_data%5B%5C%22capture_time%5C%22%5D%5Cn%20%20%20%20))%5Cn%20%20%20%20record_id%20%3D%20cursor.lastrowid%5Cn%5Cn%20%20%20%20%23%20%E5%86%99%E5%85%A5SKU%E5%AD%90%E8%A1%A8%5Cn%20%20%20%20for%20sku%20in%20parsed_data.get(%5C%22sku_info%5C%22%2C%20%5B%5D)%3A%5Cn%20%20%20%20%20%20%20%20sku_sql%20%3D%20&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20%20%20%20%20INSERT%20INTO%20goods_sku(goods_record_id%2Csku_id%2Csku_price%2Csku_original_price%2Csku_properties%2Csku_stock)%5Cn%20%20%20%20%20%20%20%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5Cn%20%20%20%20%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20%20%20%20%20cursor.execute(sku_sql%2C%20(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20record_id%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_id%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20float(sku%5B%5C%22sku_price%5C%22%5D)%20if%20sku.get(%5C%22sku_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20float(sku%5B%5C%22sku_original_price%5C%22%5D)%20if%20sku.get(%5C%22sku_original_price%5C%22)%20else%200%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_properties%5C%22%5D%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sku%5B%5C%22sku_stock%5C%22%5D%5Cn%20%20%20%20%20%20%20%20))%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20print(f%5C%22%E3%80%90%7Bparsed_data%5B&amp;#39;capture_time&amp;#39;%5D%7D%E3%80%91%E5%95%86%E5%93%81%20%7Bparsed_data%5B&amp;#39;num_iid&amp;#39;%5D%7D%20%E6%95%B0%E6%8D%AE%E5%85%A5%E5%BA%93%E5%AE%8C%E6%88%90%5C%22)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;init_db():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;初始化数据库表&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;商品主记录表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;goods_record&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;AUTOINCREMENT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;num_iid&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;orginal_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;brand&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shop_name&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;capture_time&amp;nbsp;TEXT
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;SKU规格子表，存储多规格数据
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;goods_sku&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;INTEGER&amp;nbsp;PRIMARY&amp;nbsp;KEY&amp;nbsp;AUTOINCREMENT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;goods_record_id&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_id&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_original_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_properties&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_stock&amp;nbsp;INTEGER
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()


def&amp;nbsp;save_to_db(parsed_data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;将解析后的数据写入数据库&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;not&amp;nbsp;parsed_data:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor&amp;nbsp;=&amp;nbsp;conn.cursor()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;写入主表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;insert_sql&amp;nbsp;=&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_record(num_iid,&amp;nbsp;title,&amp;nbsp;price,&amp;nbsp;orginal_price,&amp;nbsp;stock,&amp;nbsp;brand,&amp;nbsp;shop_name,&amp;nbsp;capture_time)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?,?,?)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(insert_sql,&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;num_iid&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;title&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;orginal_price&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;stock&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;brand&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;shop_name&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed_data[&amp;quot;capture_time&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;record_id&amp;nbsp;=&amp;nbsp;cursor.lastrowid

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;写入SKU子表
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;sku&amp;nbsp;in&amp;nbsp;parsed_data.get(&amp;quot;sku_info&amp;quot;,&amp;nbsp;[]):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku_sql&amp;nbsp;=&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INSERT&amp;nbsp;INTO&amp;nbsp;goods_sku(goods_record_id,sku_id,sku_price,sku_original_price,sku_properties,sku_stock)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cursor.execute(sku_sql,&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;record_id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_id&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float(sku[&amp;quot;sku_price&amp;quot;])&amp;nbsp;if&amp;nbsp;sku.get(&amp;quot;sku_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float(sku[&amp;quot;sku_original_price&amp;quot;])&amp;nbsp;if&amp;nbsp;sku.get(&amp;quot;sku_original_price&amp;quot;)&amp;nbsp;else&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_properties&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sku[&amp;quot;sku_stock&amp;quot;]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;【{parsed_data[&amp;#39;capture_time&amp;#39;]}】商品&amp;nbsp;{parsed_data[&amp;#39;num_iid&amp;#39;]}&amp;nbsp;数据入库完成&amp;quot;)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;四、简单预警逻辑 + 定时循环监控&lt;/h2&gt;&lt;p&gt;增加简单预警逻辑：当价格下跌超过阈值，或者库存变为 0 的时候打印提醒，实际项目可以扩展为邮件、企业微信消息推送。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20check_warning(data)%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E7%AE%80%E5%8D%95%E4%B8%9A%E5%8A%A1%E9%A2%84%E8%AD%A6%E9%80%BB%E8%BE%91%5C%22%5C%22%5C%22%5Cn%20%20%20%20current_price%20%3D%20data.get(%5C%22price%5C%22%2C%200)%5Cn%20%20%20%20stock%20%3D%20data.get(%5C%22stock%5C%22%2C0)%5Cn%20%20%20%20threshold_rate%20%3D%200.8%20%20%23%20%E4%BD%8E%E4%BA%8E%E5%8E%9F%E4%BB%B780%25%E8%A7%A6%E5%8F%91%E9%99%8D%E4%BB%B7%E6%8F%90%E9%86%92%5Cn%20%20%20%20original%20%3D%20data.get(%5C%22orginal_price%5C%22%2C%200)%5Cn%5Cn%20%20%20%20if%20original%20%3E%200%20and%20current_price%20%3C%3D%20original%20*%20threshold_rate%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E9%99%8D%E4%BB%B7%E9%A2%84%E8%AD%A6%EF%BC%81%E5%95%86%E5%93%81%EF%BC%9A%7Bdata%5B&amp;#39;title&amp;#39;%5D%7D%EF%BC%8C%E7%8E%B0%E4%BB%B7%7Bcurrent_price%7D%EF%BC%8C%E5%8E%9F%E4%BB%B7%7Boriginal%7D%5C%22)%5Cn%20%20%20%20if%20stock%20%3C%3D%200%3A%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E2%9A%A0%EF%B8%8F%E5%BA%93%E5%AD%98%E9%A2%84%E8%AD%A6%EF%BC%81%E5%95%86%E5%93%81%20%7Bdata%5B&amp;#39;title&amp;#39;%5D%7D%20%E5%BD%93%E5%89%8D%E5%BA%93%E5%AD%98%E4%B8%BA0%EF%BC%81%5C%22)%5Cn%5Cn%5Cndef%20monitor_loop()%3A%5Cn%20%20%20%20%5C%22%5C%22%5C%22%E6%8C%81%E7%BB%AD%E7%9B%91%E6%8E%A7%E4%B8%BB%E5%BE%AA%E7%8E%AF%5C%22%5C%22%5C%22%5Cn%20%20%20%20init_db()%5Cn%20%20%20%20print(%5C%22%E5%95%86%E5%93%81%E7%9B%91%E6%8E%A7%E7%A8%8B%E5%BA%8F%E5%B7%B2%E5%90%AF%E5%8A%A8...%5C%22)%5Cn%20%20%20%20while%20True%3A%5Cn%20%20%20%20%20%20%20%20raw%20%3D%20fetch_goods_data(TARGET_GOODS_ID)%5Cn%20%20%20%20%20%20%20%20parsed%20%3D%20parse_goods_info(raw)%5Cn%20%20%20%20%20%20%20%20if%20parsed%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20save_to_db(parsed)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20check_warning(parsed)%5Cn%20%20%20%20%20%20%20%20time.sleep(MONITOR_INTERVAL)%5Cn%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20monitor_loop()%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;check_warning(data):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;简单业务预警逻辑&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;current_price&amp;nbsp;=&amp;nbsp;data.get(&amp;quot;price&amp;quot;,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;=&amp;nbsp;data.get(&amp;quot;stock&amp;quot;,0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;threshold_rate&amp;nbsp;=&amp;nbsp;0.8&amp;nbsp;&amp;nbsp;#&amp;nbsp;低于原价80%触发降价提醒
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;original&amp;nbsp;=&amp;nbsp;data.get(&amp;quot;orginal_price&amp;quot;,&amp;nbsp;0)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;original&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;and&amp;nbsp;current_price&amp;nbsp;&amp;lt;=&amp;nbsp;original&amp;nbsp;*&amp;nbsp;threshold_rate:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️降价预警！商品：{data[&amp;#39;title&amp;#39;]}，现价{current_price}，原价{original}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;stock&amp;nbsp;&amp;lt;=&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;⚠️库存预警！商品&amp;nbsp;{data[&amp;#39;title&amp;#39;]}&amp;nbsp;当前库存为0！&amp;quot;)


def&amp;nbsp;monitor_loop():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;持续监控主循环&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init_db()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;商品监控程序已启动...&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while&amp;nbsp;True:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;raw&amp;nbsp;=&amp;nbsp;fetch_goods_data(TARGET_GOODS_ID)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;parsed&amp;nbsp;=&amp;nbsp;parse_goods_info(raw)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;parsed:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_to_db(parsed)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;check_warning(parsed)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;time.sleep(MONITOR_INTERVAL)


if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;monitor_loop()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;运行脚本之后，程序就会按照设定时间周期，自动拉取商品全部结构化信息，存入本地 sqlite 数据库，一旦触发降价、无库存条件就会打印告警信息。&lt;/p&gt;&lt;h2&gt;五、数据分析：读取历史记录，导出报表&lt;/h2&gt;&lt;p&gt;积累一段时间数据之后，我们可以读取数据库，做简单统计分析，导出 Excel 报表，方便运营复盘、看价格波动走势。 新建 analysis.py 文件：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;4&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20analysis.py%5Cnimport%20sqlite3%5Cnimport%20pandas%20as%20pd%5Cn%5Cndef%20export_history(goods_id)%3A%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22goods_monitor.db%5C%22)%5Cn%20%20%20%20df%20%3D%20pd.read_sql(&amp;#39;&amp;#39;&amp;#39;%5Cn%20%20%20%20%20%20%20%20SELECT%20*%20FROM%20goods_record%20WHERE%20num_iid%20%3D%20%3F%20ORDER%20BY%20capture_time%5Cn%20%20%20%20&amp;#39;&amp;#39;&amp;#39;%2C%20conn%2C%20params%3D(goods_id%2C))%5Cn%20%20%20%20conn.close()%5Cn%20%20%20%20if%20len(df)%20%3E%200%3A%5Cn%20%20%20%20%20%20%20%20out_file%20%3D%20f%5C%22goods_%7Bgoods_id%7D_history.xlsx%5C%22%5Cn%20%20%20%20%20%20%20%20df.to_excel(out_file%2C%20index%3DFalse)%5Cn%20%20%20%20%20%20%20%20print(f%5C%22%E5%8E%86%E5%8F%B2%E6%95%B0%E6%8D%AE%E5%AF%BC%E5%87%BA%E5%AE%8C%E6%88%90%EF%BC%8C%E6%96%87%E4%BB%B6%3A%7Bout_file%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E4%BB%B7%E6%A0%BC%E7%AE%80%E5%8D%95%E7%BB%9F%E8%AE%A1%EF%BC%9A%5C%22)%5Cn%20%20%20%20%20%20%20%20print(df%5B%5B%5C%22price%5C%22%2C%5C%22orginal_price%5C%22%2C%5C%22stock%5C%22%5D%5D.describe())%5Cn%20%20%20%20else%3A%5Cn%20%20%20%20%20%20%20%20print(%5C%22%E6%9A%82%E6%97%A0%E7%9B%91%E6%8E%A7%E8%AE%B0%E5%BD%95%5C%22)%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20export_history(%5C%2210335871600%5C%22)%5Cn%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;analysis.py
import&amp;nbsp;sqlite3
import&amp;nbsp;pandas&amp;nbsp;as&amp;nbsp;pd

def&amp;nbsp;export_history(goods_id):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;goods_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df&amp;nbsp;=&amp;nbsp;pd.read_sql(&amp;#39;&amp;#39;&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;goods_record&amp;nbsp;WHERE&amp;nbsp;num_iid&amp;nbsp;=&amp;nbsp;?&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;capture_time
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;#39;&amp;#39;&amp;#39;,&amp;nbsp;conn,&amp;nbsp;params=(goods_id,))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;len(df)&amp;nbsp;&amp;gt;&amp;nbsp;0:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;out_file&amp;nbsp;=&amp;nbsp;f&amp;quot;goods_{goods_id}_history.xlsx&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;df.to_excel(out_file,&amp;nbsp;index=False)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;历史数据导出完成，文件:{out_file}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;价格简单统计：&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(df[[&amp;quot;price&amp;quot;,&amp;quot;orginal_price&amp;quot;,&amp;quot;stock&amp;quot;]].describe())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(&amp;quot;暂无监控记录&amp;quot;)

if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;export_history(&amp;quot;10335871600&amp;quot;)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;执行后，会生成一份 Excel 文件，里面保存全部时间点采集的价格、库存、品牌店铺等记录，我们可以直接用 Excel 绘制价格折线图，直观看到一段时间商品价格波动变化。&lt;/p&gt;&lt;h2&gt;六、实际业务拓展思路&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;批量监控多个商品&lt;/strong&gt;：把商品 ID 放到列表或者配置文件，循环遍历采集，实现批量竞品巡检；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;告警升级&lt;/strong&gt;：把 print 打印告警改为企业微信、钉钉机器人推送，不用守着控制台；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;数据清洗优化&lt;/strong&gt;：SKU 多规格价格做单独监控，跟踪每个规格的价格、库存变化；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;部署运行&lt;/strong&gt;：Linux 服务器使用 nohup 后台运行脚本，实现 7×24 小时不间断监控；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;可视化看板&lt;/strong&gt;：读取 sqlite 数据接入 Grafana，直接做网页端可视化监控大盘。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;七、踩坑经验总结&lt;/h2&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;价格字段偶尔会存在偏差，属于平台页面动态渲染问题，做分析的时候建议多对比连续多次采集结果，不要单次结果直接判定；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;密钥一定做好保管，公开代码不要明文上传；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;设置合理采集间隔，不要高频请求，避免触发限制；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;部分非自营店铺，部分店铺相关字段不会返回，代码做好判空，防止脚本报错中断；&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;商品下架时，返回内容会缺少主体信息，脚本做好空判断，保证循环不会崩溃。&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2&gt;写在最后&lt;/h2&gt;&lt;p&gt;很多人一听到商品监控，就会觉得写页面解析、处理反爬会非常麻烦。借助 OpenClaw 把页面转为结构化数据，我们就不用再纠缠网页 DOM 结构、验证码、签名等复杂底层问题，开发者只需要把精力聚焦业务：存储、统计、预警、报表输出。&lt;/p&gt;&lt;p&gt;对于中小团队或者个人开发者，这套方案成本很低，几十行代码快速落地商品监控，把运营人员从复制粘贴表格这种重复劳动解放出来，把时间真正留给业务决策。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 10:52:16 +0800</pubDate></item><item><title>用 OpenClaw 把京东商品数据做成自动监控报表，全程不到一小时</title><link>https://www.shopapi.com.cn/?id=232</link><description>&lt;p&gt;&lt;span data-cke-copybin-start=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;做电商运营或者盯竞品的朋友应该都有体会：想搞清楚某个商品今天什么价、库存还剩多少、月销涨了没，最笨的办法就是收藏夹里存几十个链接，隔三差五挨个点开看。以前我也这么干，直到有天发现自己一天要重复打开同一个商品页七八次，实在绷不住了，决定搞个脚本把这些活全自动掉。&lt;/p&gt;&lt;p&gt;这篇文章记录一下我是怎么用 OpenClaw 这个数据工具把京东商品信息拿回来，再做成一个能每天自动跑、自动记录变化的监控小系统的。全程不引什么复杂框架，一个 Python 脚本为主，能跑就行。&lt;/p&gt;&lt;h2&gt;先解决数据从哪来的问题&lt;/h2&gt;&lt;p&gt;自己写爬虫去抓京东商品页，不是不行，但谁弄谁知道：各种加密参数、验证、反爬策略，光调试就得耗掉一两天，而且页面结构一改就得跟着改，维护成本很高。我的诉求很简单——拿商品ID换回结构化的商品信息（标题、价格、库存、销量、SKU、店铺、图片这些），越省事越好。&lt;/p&gt;&lt;p&gt;后来换用 OpenClaw 这种现成的数据通道，思路就完全不一样了：拿 &lt;span aria-label=&quot;a 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_inline cke_widget_csdnlink cke_widget_wrapper_link-info cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;a&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;6&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;link-info cke_widget_editable cke_widget_element&quot; contenteditable=&quot;true&quot; data-cke-enter-mode=&quot;2&quot; data-cke-widget-data=&quot;%7B%22url%22%3A%22https%3A%2F%2Fo0b.cn%2Ficris%22%2C%22text%22%3A%22key%20%E5%92%8C%20secret%22%2C%22desc%22%3A%22%22%2C%22icon%22%3A%22%22%2C%22isCard%22%3Afalse%2C%22hasResquest%22%3Atrue%2C%22iconDefault%22%3A%22https%3A%2F%2Fcsdnimg.cn%2Frelease%2Fblog_editor_html%2Frelease2.4.6%2Fckeditor%2Fplugins%2FCsdnLink%2Ficons%2Ficon-default.png%3Ft%3DQ239%22%2C%22id%22%3A%227edcBU-1790560307604%22%2C%22classes%22%3A%7B%22link-info%22%3A1%7D%7D&quot; data-cke-widget-editable=&quot;text&quot; data-cke-widget-keep-attr=&quot;0&quot; data-link-icon=&quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/CsdnLink/icons/icon-default.png?t=Q239&quot; data-link-title=&quot;key 和 secret&quot; data-widget=&quot;csdnlink&quot; href=&quot;https://o0b.cn/icris&quot; title=&quot;key 和 secret&quot;&gt;&lt;strong&gt;key 和 secret&lt;/strong&gt;&lt;/a&gt;&lt;/span&gt; 换一个请求地址，把商品ID传过去，返回的就是整理好的 JSON，字段清清楚楚。省下的时间全花在正事上——做监控和分析。&lt;/p&gt;&lt;h2&gt;先看看一次请求能拿到什么&lt;/h2&gt;&lt;p&gt;OpenClaw 里京东商品详情这块，入口参数就一个核心的：商品ID（就是链接里那段数字，比如 100126351037）。请求地址大概是这个套路（地址和密钥开通之后会拿到，下面先用占位符表示）：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;0&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22BASE_URL%20%3D%20%5C%22https%3A%2F%2F%E4%BD%A0%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%9C%B0%E5%9D%80%2Fjd%2Fitem_get_pro%5C%22%5CnKEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84key%5C%22%5CnSECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84secret%5C%22%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;BASE_URL&amp;nbsp;=&amp;nbsp;&amp;quot;https://你的服务地址/jd/item_get_pro&amp;quot;
KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的key&amp;quot;
SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的secret&amp;quot;&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;带上参数发个 GET 请求，返回的 JSON 里关键的字段大概是这些：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;title 商品标题，一眼看出是什么东西&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;price / orginal_price 当前价和原价（注意文档里也写了，价格字段存在对不上的情况，后面会讲怎么处理）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;num 库存数量&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;sales 销量，很多商品有这个字段，可以拿来做趋势&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;skus 规格列表，每个规格的 id、价格、库存都在里面，多规格商品要重点看这个&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;item_imgs 商品图片&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;seller_info 店铺信息&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;location 发货地，选品的时候有用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;这些字段基本覆盖了日常监控 90% 的需求。下面直接上代码。&lt;/p&gt;&lt;h2&gt;写一个最朴素的取数脚本&lt;/h2&gt;&lt;p&gt;依赖就一个 requests，装好就能跑：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;1&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22import%20requests%5Cn%5CnBASE_URL%20%3D%20%5C%22https%3A%2F%2F%E4%BD%A0%E7%9A%84%E6%9C%8D%E5%8A%A1%E5%9C%B0%E5%9D%80%2Fjd%2Fitem_get_pro%5C%22%5CnKEY%20%3D%20%5C%22%E4%BD%A0%E7%9A%84key%5C%22%5CnSECRET%20%3D%20%5C%22%E4%BD%A0%E7%9A%84secret%5C%22%5Cn%5Cndef%20fetch_item(num_iid)%3A%5Cn%20%20%20%20params%20%3D%20%7B%5Cn%20%20%20%20%20%20%20%20%5C%22key%5C%22%3A%20KEY%2C%5Cn%20%20%20%20%20%20%20%20%5C%22secret%5C%22%3A%20SECRET%2C%5Cn%20%20%20%20%20%20%20%20%5C%22num_iid%5C%22%3A%20num_iid%2C%5Cn%20%20%20%20%7D%5Cn%20%20%20%20resp%20%3D%20requests.get(BASE_URL%2C%20params%3Dparams%2C%20timeout%3D10)%5Cn%20%20%20%20resp.raise_for_status()%5Cn%20%20%20%20data%20%3D%20resp.json()%5Cn%20%20%20%20return%20data.get(%5C%22item%5C%22)%20or%20%7B%7D%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;import&amp;nbsp;requests

BASE_URL&amp;nbsp;=&amp;nbsp;&amp;quot;https://你的服务地址/jd/item_get_pro&amp;quot;
KEY&amp;nbsp;=&amp;nbsp;&amp;quot;你的key&amp;quot;
SECRET&amp;nbsp;=&amp;nbsp;&amp;quot;你的secret&amp;quot;

def&amp;nbsp;fetch_item(num_iid):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;key&amp;quot;:&amp;nbsp;KEY,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;secret&amp;quot;:&amp;nbsp;SECRET,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;num_iid&amp;quot;:&amp;nbsp;num_iid,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp&amp;nbsp;=&amp;nbsp;requests.get(BASE_URL,&amp;nbsp;params=params,&amp;nbsp;timeout=10)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;resp.raise_for_status()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;data&amp;nbsp;=&amp;nbsp;resp.json()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;data.get(&amp;quot;item&amp;quot;)&amp;nbsp;or&amp;nbsp;{}&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;第一次跑通的时候还挺有成就感的，一个商品ID换回一坨结构化数据，直接能用。接下来就顺手把它存下来。&lt;/p&gt;&lt;h2&gt;把快照存下来&lt;/h2&gt;&lt;p&gt;监控的前提是有历史。我的做法很简单：每次取数就把当天的&amp;quot;快照&amp;quot;追加到 SQLite 里，一张表搞定，不用上什么重型数据库。&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;2&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22import%20sqlite3%5Cnfrom%20datetime%20import%20datetime%5Cn%5Cndef%20init_db()%3A%5Cn%20%20%20%20conn%20%3D%20sqlite3.connect(%5C%22jd_monitor.db%5C%22)%5Cn%20%20%20%20conn.execute(%5C%22%5C%22%5C%22%5Cn%20%20%20%20%20%20%20%20CREATE%20TABLE%20IF%20NOT%20EXISTS%20snapshots%20(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20num_iid%20%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20ts%20%20%20%20%20%20%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20title%20%20%20%20TEXT%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20price%20%20%20%20REAL%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20orginal_price%20REAL%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20stock%20%20%20%20INTEGER%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20sales%20%20%20%20INTEGER%5Cn%20%20%20%20%20%20%20%20)%5Cn%20%20%20%20%5C%22%5C%22%5C%22)%5Cn%20%20%20%20conn.commit()%5Cn%20%20%20%20return%20conn%5Cn%5Cndef%20save_snapshot(conn%2C%20num_iid%2C%20item)%3A%5Cn%20%20%20%20conn.execute(%5Cn%20%20%20%20%20%20%20%20%5C%22INSERT%20INTO%20snapshots%20(num_iid%2C%20ts%2C%20title%2C%20price%2C%20orginal_price%2C%20stock%2C%20sales)%20VALUES%20(%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F%2C%3F)%5C%22%2C%5Cn%20%20%20%20%20%20%20%20(%5Cn%20%20%20%20%20%20%20%20%20%20%20%20num_iid%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20datetime.now().strftime(%5C%22%25Y-%25m-%25d%20%25H%3A%25M%3A%25S%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item.get(%5C%22title%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item.get(%5C%22price%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item.get(%5C%22orginal_price%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item.get(%5C%22num%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item.get(%5C%22sales%5C%22)%2C%5Cn%20%20%20%20%20%20%20%20)%2C%5Cn%20%20%20%20)%5Cn%20%20%20%20conn.commit()%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;import&amp;nbsp;sqlite3
from&amp;nbsp;datetime&amp;nbsp;import&amp;nbsp;datetime

def&amp;nbsp;init_db():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;sqlite3.connect(&amp;quot;jd_monitor.db&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.execute(&amp;quot;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;CREATE&amp;nbsp;TABLE&amp;nbsp;IF&amp;nbsp;NOT&amp;nbsp;EXISTS&amp;nbsp;snapshots&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;num_iid&amp;nbsp;&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ts&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;title&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TEXT,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;price&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;orginal_price&amp;nbsp;REAL,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;stock&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INTEGER,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;sales&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;INTEGER
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;conn

def&amp;nbsp;save_snapshot(conn,&amp;nbsp;num_iid,&amp;nbsp;item):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.execute(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;INSERT&amp;nbsp;INTO&amp;nbsp;snapshots&amp;nbsp;(num_iid,&amp;nbsp;ts,&amp;nbsp;title,&amp;nbsp;price,&amp;nbsp;orginal_price,&amp;nbsp;stock,&amp;nbsp;sales)&amp;nbsp;VALUES&amp;nbsp;(?,?,?,?,?,?,?)&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;num_iid,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;datetime.now().strftime(&amp;quot;%Y-%m-%d&amp;nbsp;%H:%M:%S&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;title&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;price&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;orginal_price&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;num&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item.get(&amp;quot;sales&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.commit()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;定时自动跑&lt;/h2&gt;&lt;p&gt;我自己是每天 9 点、14 点、21 点各跑一次，用系统自带的定时任务就够了，没必要为了这个专门引一个调度框架：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;3&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22%23%20crontab%20%E9%87%8C%E5%8A%A0%E4%B8%89%E8%A1%8C%E5%8D%B3%E5%8F%AF%5Cn%23%200%209%20*%20*%20*%20cd%20%2Fpath%2Fto%2Fscript%20%26%26%20python3%20monitor.py%5Cn%23%200%2014%20*%20*%20*%20cd%20%2Fpath%2Fto%2Fscript%20%26%26%20python3%20monitor.py%5Cn%23%200%2021%20*%20*%20*%20cd%20%2Fpath%2Fto%2Fscript%20%26%26%20python3%20monitor.py%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;#&amp;nbsp;crontab&amp;nbsp;里加三行即可
#&amp;nbsp;0&amp;nbsp;9&amp;nbsp;*&amp;nbsp;*&amp;nbsp;*&amp;nbsp;cd&amp;nbsp;/path/to/script&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;python3&amp;nbsp;monitor.py
#&amp;nbsp;0&amp;nbsp;14&amp;nbsp;*&amp;nbsp;*&amp;nbsp;*&amp;nbsp;cd&amp;nbsp;/path/to/script&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;python3&amp;nbsp;monitor.py
#&amp;nbsp;0&amp;nbsp;21&amp;nbsp;*&amp;nbsp;*&amp;nbsp;*&amp;nbsp;cd&amp;nbsp;/path/to/script&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;python3&amp;nbsp;monitor.py&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;monitor.py 的主逻辑就是遍历我收藏的那批商品ID，逐个取数、落库：&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;4&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22WATCH_LIST%20%3D%20%5B%5C%2210335871600%5C%22%2C%20%5C%22100126351037%5C%22%5D%20%20%23%20%E6%8D%A2%E6%88%90%E4%BD%A0%E8%A6%81%E7%9B%AF%E7%9A%84%E5%95%86%E5%93%81ID%5Cn%5Cndef%20main()%3A%5Cn%20%20%20%20conn%20%3D%20init_db()%5Cn%20%20%20%20for%20num_iid%20in%20WATCH_LIST%3A%5Cn%20%20%20%20%20%20%20%20try%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20item%20%3D%20fetch_item(num_iid)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20save_snapshot(conn%2C%20num_iid%2C%20item)%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%5BOK%5D%20%7Bnum_iid%7D%20%7Bitem.get(&amp;#39;title&amp;#39;%2C%20&amp;#39;&amp;#39;)%5B%3A20%5D%7D%20%7Bitem.get(&amp;#39;price&amp;#39;)%7D%5C%22)%5Cn%20%20%20%20%20%20%20%20except%20Exception%20as%20e%3A%5Cn%20%20%20%20%20%20%20%20%20%20%20%20print(f%5C%22%5BFAIL%5D%20%7Bnum_iid%7D%3A%20%7Be%7D%5C%22)%5Cn%20%20%20%20conn.close()%5Cn%5Cnif%20__name__%20%3D%3D%20%5C%22__main__%5C%22%3A%5Cn%20%20%20%20main()%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;WATCH_LIST&amp;nbsp;=&amp;nbsp;[&amp;quot;10335871600&amp;quot;,&amp;nbsp;&amp;quot;100126351037&amp;quot;]&amp;nbsp;&amp;nbsp;#&amp;nbsp;换成你要盯的商品ID

def&amp;nbsp;main():
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn&amp;nbsp;=&amp;nbsp;init_db()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;num_iid&amp;nbsp;in&amp;nbsp;WATCH_LIST:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;item&amp;nbsp;=&amp;nbsp;fetch_item(num_iid)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;save_snapshot(conn,&amp;nbsp;num_iid,&amp;nbsp;item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;[OK]&amp;nbsp;{num_iid}&amp;nbsp;{item.get(&amp;#39;title&amp;#39;,&amp;nbsp;&amp;#39;&amp;#39;)[:20]}&amp;nbsp;{item.get(&amp;#39;price&amp;#39;)}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;except&amp;nbsp;Exception&amp;nbsp;as&amp;nbsp;e:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(f&amp;quot;[FAIL]&amp;nbsp;{num_iid}:&amp;nbsp;{e}&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;conn.close()

if&amp;nbsp;__name__&amp;nbsp;==&amp;nbsp;&amp;quot;__main__&amp;quot;:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;main()&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;h2&gt;让数据自己说话&lt;/h2&gt;&lt;p&gt;跑了几天之后，数据开始有点意思了。比如我能直接回答这种问题：这个商品最近 7 天最低价是多少？什么时候降的？SKU 里的某个规格是不是一直缺货？&lt;/p&gt;&lt;div aria-label=&quot;代码段 小部件&quot; class=&quot;cke_widget_wrapper cke_widget_block cke_widget_codeSnippet cke_widget_selected&quot; contenteditable=&quot;false&quot; data-cke-display-name=&quot;代码段&quot; data-cke-filter=&quot;off&quot; data-cke-widget-id=&quot;5&quot; data-cke-widget-wrapper=&quot;1&quot; role=&quot;region&quot; tabindex=&quot;-1&quot;&gt;&lt;pre class=&quot;cke_widget_element&quot; data-cke-widget-data=&quot;%7B%22classes%22%3Anull%2C%22lang%22%3Anull%2C%22code%22%3A%22def%20price_trend(conn%2C%20num_iid)%3A%5Cn%20%20%20%20rows%20%3D%20conn.execute(%5Cn%20%20%20%20%20%20%20%20%5C%22%5C%22%5C%22SELECT%20ts%2C%20price%2C%20stock%2C%20sales%20FROM%20snapshots%5Cn%20%20%20%20%20%20%20%20%20%20%20WHERE%20num_iid%3D%3F%20ORDER%20BY%20ts%5C%22%5C%22%5C%22%2C%5Cn%20%20%20%20%20%20%20%20(num_iid%2C)%2C%5Cn%20%20%20%20).fetchall()%5Cn%20%20%20%20for%20ts%2C%20price%2C%20stock%2C%20sales%20in%20rows%3A%5Cn%20%20%20%20%20%20%20%20print(ts%2C%20%5C%22%E4%BB%B7%E6%A0%BC%3A%5C%22%2C%20price%2C%20%5C%22%E5%BA%93%E5%AD%98%3A%5C%22%2C%20stock%2C%20%5C%22%E9%94%80%E9%87%8F%3A%5C%22%2C%20sales)%22%7D&quot; data-cke-widget-keep-attr=&quot;0&quot; data-widget=&quot;codeSnippet&quot;&gt;def&amp;nbsp;price_trend(conn,&amp;nbsp;num_iid):
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;rows&amp;nbsp;=&amp;nbsp;conn.execute(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;&amp;quot;&amp;quot;SELECT&amp;nbsp;ts,&amp;nbsp;price,&amp;nbsp;stock,&amp;nbsp;sales&amp;nbsp;FROM&amp;nbsp;snapshots
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WHERE&amp;nbsp;num_iid=?&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;ts&amp;quot;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(num_iid,),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;).fetchall()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for&amp;nbsp;ts,&amp;nbsp;price,&amp;nbsp;stock,&amp;nbsp;sales&amp;nbsp;in&amp;nbsp;rows:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;print(ts,&amp;nbsp;&amp;quot;价格:&amp;quot;,&amp;nbsp;price,&amp;nbsp;&amp;quot;库存:&amp;quot;,&amp;nbsp;stock,&amp;nbsp;&amp;quot;销量:&amp;quot;,&amp;nbsp;sales)&lt;/pre&gt;&lt;span class=&quot;cke_reset cke_widget_drag_handler_container&quot; style=&quot;background: url(&amp;quot;https://csdnimg.cn/release/blog_editor_html/release2.4.6/ckeditor/plugins/widget/images/handle.png&amp;quot;) rgba(220, 220, 220, 0.5); top: 0px; left: 0px;&quot;&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;想更直观一点，用 matplotlib 画个价格曲线，几行代码的事，这里就不展开贴图了。降价提醒也可以做：如果最新价格比历史最低价还低，就往群里发个通知，抢券补货都来得及。&lt;/p&gt;&lt;h2&gt;踩过的坑，帮你提前避雷&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1.价格字段别全信&lt;/strong&gt;。文档里原话是&amp;quot;当前商品id价格【价格会对不上】&amp;quot;，尤其多规格商品，price 这个字段可能是某个规格的价，真正准的是 skus 里逐条对。拿不准的时候就遍历 skus，按你自己的规则取价。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;2.有缓存&lt;/strong&gt;。默认走的是缓存数据，速度快但可能不是最新，想要更实时的数据记得把 cache 参数关掉（cache=no），代价是慢一点、调用量上去了，看你的场景取舍。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3.别高频猛打&lt;/strong&gt;。我一开始图省事写了个 5 分钟一轮的循环，结果跑一会儿就报错被限了。监控类需求几分钟和几小时的差别其实没那么大，改成每天固定几次，稳稳的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;4.商品下架/改ID&lt;/strong&gt;。有些商品链接会失效，脚本里记得 try/except，单条失败别让整个任务崩掉，日志打出来就行。&lt;/p&gt;&lt;h2&gt;最后说两句&lt;/h2&gt;&lt;p&gt;其实整个过程拆开看没什么高深的：一个现成的数据通道解决取数，SQLite 解决存储，cron 解决定时，剩下的就是根据你自己的业务去定义&amp;quot;什么变化值得关注&amp;quot;。这套东西我跑了快一个月，省下来的时间早就够写这篇文章了。&lt;/p&gt;&lt;p&gt;&lt;span data-cke-copybin-end=&quot;1&quot;&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;</description><pubDate>Mon, 28 Sep 2026 09:57:23 +0800</pubDate></item></channel></rss>