Python爬虫框架实战:从零构建健壮数据采集系统

news2026/5/3 11:26:31
1. 项目概述与核心价值最近在折腾一些自动化脚本时发现了一个挺有意思的项目叫yuanjl34/copaw-liexiaoyi。乍一看这个仓库名可能有点摸不着头脑但点进去研究后发现它其实是一个围绕“猎小蚁”这个核心概念展开的、用于特定场景数据采集与处理的工具集。对于需要从特定公开渠道比如某些电商平台、社交媒体或信息聚合站点进行结构化数据抓取和分析的朋友来说这个项目提供了一套相对完整的思路和可复用的代码模块。它不是那种开箱即用、填个网址就能跑的通用爬虫而是更偏向于一个“脚手架”或“解决方案框架”你需要根据自己目标网站的结构去调整和填充其中的解析逻辑。这种设计思路其实很实用因为它把网络请求、并发控制、数据存储这些脏活累活都封装好了你只需要专注于最核心也最易变的页面解析部分开发效率会高很多。我自己也做过不少类似的数据抓取项目深知从零开始搭建的麻烦你得处理请求头、管理Cookie和Session、设计重试机制、应对反爬策略、考虑数据去重和存储格式……一套流程下来没个两三天搞不定基础框架。而copaw-liexiaoyi这类项目相当于有人帮你把地基打好了你直接在上面盖房子就行。它特别适合那些有一定Python基础想快速切入某个垂直领域数据采集的开发者、数据分析师或是业务运营人员。通过这个项目你不仅能拿到数据更能学到一套如何优雅地组织一个中小型爬虫项目的工程化方法比如如何模块化、如何配置化、如何写得更健壮以便于长期维护。接下来我就结合自己的经验把这个项目的设计思路、核心模块、实操要点以及可能遇到的坑给大家掰开揉碎了讲清楚。2. 项目架构与设计思路拆解2.1 核心模块构成与职责分离copaw-liexiaoyi的代码结构通常遵循一个清晰的分层或模块化设计这是保证项目可维护性和可扩展性的关键。虽然不同版本的实现可能有差异但一个健壮的爬虫框架通常会包含以下几个核心模块调度器与任务管理模块这是整个项目的大脑。它负责读取配置比如要抓取的初始URL列表、关键词等生成具体的抓取任务并将任务分发给下游的爬虫Worker。一个设计良好的调度器还需要处理任务队列、优先级调度以及任务去重防止同一个页面被重复抓取。在分布式或高并发场景下这个模块可能会与消息队列如Redis结合使用。网络请求与下载器模块这是项目的四肢。它封装了HTTP请求的所有细节比如使用requests或aiohttp库发送请求、设置User-Agent、处理Cookie和Session、管理代理IP池、实现自动重试逻辑应对网络波动或短暂的反爬拦截以及控制请求频率遵守robots.txt并避免对目标网站造成压力。这个模块的稳定性和灵活性直接决定了爬虫的抓取成功率和速度。页面解析与提取器模块这是项目的眼睛和手也是你需要投入最多精力进行定制化的部分。它的任务是从下载器返回的HTML或JSON响应中提取出我们关心的结构化数据。这里会大量用到BeautifulSoup、lxml或parsel这样的HTML解析库以及json模块。好的提取器应该是高内聚、低耦合的一个类或函数最好只负责解析一种页面模板如列表页、详情页这样当网站改版时你只需要修改对应的解析器而不影响其他部分。数据清洗与存储模块这是项目的肠胃。原始提取的数据往往包含空白字符、无效条目、重复内容或格式不一致的问题清洗模块负责过滤和标准化这些数据。之后存储模块将干净的数据持久化常见的存储后端包括文件CSV、JSON Lines、数据库MySQL、PostgreSQL、MongoDB或数据仓库。这个模块的设计要考虑数据schema的定义、写入性能以及后续查询的便利性。配置与日志模块这是项目的神经系统和黑匣子。通过配置文件如config.yaml或.env文件来管理数据库连接字符串、请求间隔、代理设置、关键词列表等可变参数使得项目无需修改代码就能适应不同环境或任务。日志模块则至关重要它需要详细记录爬虫运行过程中的信息、警告和错误比如哪个任务失败了、失败原因是什么这是后期调试和监控的基石。2.2 技术选型背后的考量为什么copaw-liexiaoyi可能会选择某些技术栈这背后有很强的实用主义考量。请求库的选择如果项目是同步的requests是首选因为它简单易用、生态丰富。但如果追求极高的并发性能每秒抓取数百上千个页面那么基于异步IO的aiohttp或httpx就是更优解。不过异步编程复杂度更高需要处理事件循环、任务调度等。从项目名和常见实践推测初期版本可能基于requests后期可能引入并发优化。解析库的选择BeautifulSoup的API非常友好适合初学者和解析不太复杂的页面。lxml的解析速度最快适合处理海量页面但XPath语法需要学习。parselScrapy使用的库融合了XPath和CSS选择器的优点也很强大。选型往往取决于开发者的熟悉度和项目对性能的要求。并发与任务队列对于单机爬虫Python内置的threading多线程或multiprocessing多进程模块可以用于加速。concurrent.futures模块提供了更高级的线程池/进程池接口。当任务量巨大或需要分布式部署时引入Redis作为分布式任务队列和去重集合的存储后端是业界常见做法。数据存储CSV文件适合小型、一次性任务简单直观。如果需要复杂查询或持续增量更新SQLite轻量级或MySQL/PostgreSQL重型是可靠的选择。对于非结构化或半结构化数据MongoDB这类文档数据库更灵活。选型时需权衡数据结构复杂度、读写性能需求以及团队的技术栈。注意在设计和开发爬虫时必须严格遵守目标网站的robots.txt协议尊重网站的版权和数据所有权。避免过高频率的请求这既是法律和道德要求也能让你的爬虫运行得更长久、更稳定。建议在配置中设置合理的请求延迟如time.sleep(1)或更长并考虑使用旋转用户代理User-Agent来模拟普通浏览器行为。3. 核心模块深度解析与实操要点3.1 网络请求下载器的稳健性设计一个健壮的下载器是爬虫的命脉。它绝不能只是一个简单的requests.get()包装。让我们深入看看一个工业级下载器应该具备哪些特性以及如何在copaw-liexiaoyi中实现。1. 请求头管理与会话保持很多网站会检查请求头特别是User-Agent。一个固定的UA很容易被识别为爬虫。我们需要一个UA池每次请求随机选取一个。同时对于需要登录或保持状态的网站使用requests.Session()对象是必须的它可以自动处理Cookies。import requests from fake_useragent import UserAgent import random import time class RobustDownloader: def __init__(self): self.session requests.Session() self.ua UserAgent() # 可以自定义一个UA列表避免过度依赖第三方库 self.custom_headers_pool [ {User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36}, {User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15}, # ... 更多 ] # 设置会话级通用头 self.session.headers.update({ Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, }) def get_with_retry(self, url, max_retries3, timeout10, **kwargs): 带重试机制的GET请求 for attempt in range(max_retries): try: # 每次请求随机更新User-Agent current_headers {User-Agent: self.ua.random} # 合并传入的headers和随机UA headers kwargs.pop(headers, {}) headers.update(current_headers) kwargs[headers] headers resp self.session.get(url, timeouttimeout, **kwargs) resp.raise_for_status() # 如果状态码不是200抛出HTTPError异常 # 检查返回内容是否有效例如是否包含反爬提示 if self._is_anti_spider_page(resp.text): raise requests.RequestException(触发反爬机制) return resp except (requests.ConnectionError, requests.Timeout, requests.HTTPError) as e: print(f请求 {url} 失败第{attempt1}次重试。错误: {e}) if attempt max_retries - 1: raise # 重试次数用尽抛出异常 wait_time (2 ** attempt) random.random() # 指数退避策略 time.sleep(wait_time) return None # 理论上不会执行到这里 def _is_anti_spider_page(self, html_text): 简单检测是否为反爬页面如验证码页、跳转页 anti_keywords [验证码, access denied, robot check, 请输入验证码] return any(keyword in html_text.lower() for keyword in anti_keywords)2. 代理IP池的集成当单IP请求频率过高时使用代理IP是绕过封锁的有效手段。下载器需要能够无缝切换代理。我们可以维护一个代理IP列表每次请求随机选取并定期检测代理的有效性。class ProxyDownloader(RobustDownloader): def __init__(self, proxy_listNone): super().__init__() self.proxy_list proxy_list or [] # 格式: [http://ip:port, socks5://ip:port] self.current_proxy_index 0 def get_with_retry(self, url, max_retries3, **kwargs): for attempt in range(max_retries): try: if self.proxy_list: proxy random.choice(self.proxy_list) kwargs[proxies] {http: proxy, https: proxy} return super().get_with_retry(url, max_retries1, **kwargs) # 父类重试一次这里控制总次数 except requests.RequestException as e: print(f使用代理请求失败尝试下一个代理或直接连接。错误: {e}) # 可以在这里将失效代理从列表中移除 if kwargs.get(proxies) and self.proxy_list: failed_proxy kwargs[proxies][http] if failed_proxy in self.proxy_list: self.proxy_list.remove(failed_proxy) print(f移除失效代理: {failed_proxy}) if not self.proxy_list or attempt max_retries - 1: # 没有代理或重试用完尝试直连 print(尝试直连...) kwargs.pop(proxies, None) return super().get_with_retry(url, max_retries1, **kwargs) time.sleep(1) return None3. 请求频率控制这是体现“友好爬虫”素养的关键。绝对不要用死循环无延迟地请求。应该在下载器类中维护一个“域名字典”记录每个域名上次请求的时间从而确保两次请求之间有足够的间隔。import time from urllib.parse import urlparse from collections import defaultdict class PoliteDownloader(ProxyDownloader): def __init__(self, default_delay2.0, **kwargs): super().__init__(**kwargs) self.default_delay default_delay # 默认延迟秒数 self.domain_last_request defaultdict(float) # 记录域名上次请求时间 def get_with_retry(self, url, delayNone, **kwargs): 遵守礼貌规则的请求 domain urlparse(url).netloc last_time self.domain_last_request.get(domain, 0) elapsed time.time() - last_time wait_time (delay or self.default_delay) - elapsed if wait_time 0: print(f礼貌等待 {wait_time:.2f} 秒再请求 {domain}) time.sleep(wait_time) response super().get_with_retry(url, **kwargs) self.domain_last_request[domain] time.time() # 更新请求时间 return response3.2 页面解析器的灵活性与容错性解析器是业务逻辑最密集的地方也是最容易因为网站改版而失效的部分。因此它的设计要追求高灵活和强容错。1. 使用多种选择器备用策略不要只依赖一种定位方式比如单一的XPath。一个元素最好能用ID、Class、XPath、CSS选择器等多种方式定位并在代码中按优先级尝试。这样当一种方式失效时解析器还能通过其他方式“兜底”。from bs4 import BeautifulSoup import re class FlexibleParser: staticmethod def extract_title(html): 从HTML中提取标题尝试多种方法 soup BeautifulSoup(html, lxml) title None # 方法1: 优先查找特定的meta标签常见于商品页 meta_og_title soup.find(meta, propertyog:title) if meta_og_title and meta_og_title.get(content): title meta_og_title[content].strip() return title # 方法2: 查找特定的h1标签常见于文章详情页 h1_tag soup.find(h1, class_re.compile(r(title|heading|name))) if h1_tag: title h1_tag.get_text(stripTrue) if title and len(title) 5: # 简单有效性检查 return title # 方法3: 回退到document的title标签 if soup.title: title soup.title.string.strip() return title # 方法4: 如果以上都失败记录日志并返回空或默认值 print(警告: 未能提取到有效标题) return 未知标题2. 数据清洗与规范化提取的原始文本通常包含多余的空格、换行符、不可见字符或者价格、日期格式不统一。解析器应该集成简单的清洗逻辑。staticmethod def clean_price(price_text): 清洗价格字符串提取数字 if not price_text: return 0.0 # 移除货币符号、逗号等非数字字符保留小数点 import re cleaned re.sub(r[^\d.], , price_text) try: return float(cleaned) if cleaned else 0.0 except ValueError: return 0.0 staticmethod def normalize_date(date_text, input_formats[%Y-%m-%d, %Y/%m/%d, %d-%m-%Y]): 尝试将各种日期字符串标准化为YYYY-MM-DD格式 from datetime import datetime if not date_text: return date_text date_text.strip() for fmt in input_formats: try: dt datetime.strptime(date_text, fmt) return dt.strftime(%Y-%m-%d) except ValueError: continue # 如果所有格式都不匹配返回原始文本或进行更复杂的解析如正则 return date_text3. 解析器工厂模式如果项目需要抓取多种不同结构的页面例如电商网站的商品列表页和商品详情页强烈建议使用“工厂模式”或“注册表模式”来管理解析器。这样调度器只需要根据URL特征或任务类型获取对应的解析器即可代码结构非常清晰。class ParserFactory: _parsers {} # 注册表 classmethod def register_parser(cls, page_type): 装饰器用于注册解析器 def wrapper(parser_cls): cls._parsers[page_type] parser_cls return parser_cls return wrapper classmethod def get_parser(cls, page_type, html): 根据页面类型获取解析器实例 parser_cls cls._parsers.get(page_type) if not parser_cls: raise ValueError(f未注册的页面类型: {page_type}) return parser_cls(html) # 定义并注册列表页解析器 ParserFactory.register_parser(list_page) class ListPageParser: def __init__(self, html): self.soup BeautifulSoup(html, lxml) def extract_items(self): # 解析列表返回商品URL列表或其他元数据 items [] # ... 解析逻辑 return items # 定义并注册详情页解析器 ParserFactory.register_parser(detail_page) class DetailPageParser: def __init__(self, html): self.soup BeautifulSoup(html, lxml) def extract_detail(self): # 解析商品详情 detail { title: FlexibleParser.extract_title(str(self.soup)), price: self._extract_price(), # ... } return detail def _extract_price(self): # 私有方法处理特定的价格提取逻辑 pass # 使用示例 html downloader.get(url).text if list in url: parser ParserFactory.get_parser(list_page, html) item_urls parser.extract_items() elif product in url: parser ParserFactory.get_parser(detail_page, html) product_info parser.extract_detail()4. 完整工作流实现与配置详解4.1 从配置到执行的完整流程一个易于使用的爬虫项目应该将大部分可变参数外置到配置文件中。我们来看一个典型的config.yaml配置示例和主程序如何读取并执行。config.yaml配置文件示例# 爬虫基础配置 spider: name: liexiaoyi_product_spider start_urls: - https://example.com/category/electronics - https://example.com/category/books max_pages: 1000 # 最大抓取页面数 request_delay: 1.5 # 请求延迟秒 max_retries: 3 user_agent_type: random # 使用随机UA # 解析器配置 parser: list_page_selector: item_container: div.product-item # 列表项容器CSS选择器 link_selector: a.product-link::attr(href) # 详情页链接选择器 next_page: a.next-page::attr(href) # 下一页链接选择器 detail_page_selector: title: h1.product-title::text price: span.price::text description: div.description::text # 数据存储配置 storage: type: csv # 可选: csv, json, mysql, mongodb csv: file_path: ./data/products.csv encoding: utf-8-sig # 支持Excel打开 database: # 如果type是mysql或mongodb则填写 host: localhost port: 3306 name: spider_data table: products # 代理配置可选 proxy: enabled: false list: - http://proxy1.example.com:8080 - http://proxy2.example.com:8080 # 日志配置 logging: level: INFO file: ./logs/spider.log format: %(asctime)s - %(name)s - %(levelname)s - %(message)s主程序main.py的工作流import yaml import logging from downloader import PoliteDownloader from parser_factory import ParserFactory from storage import CSVStorage, DatabaseStorage from scheduler import Scheduler def setup_logging(config): 根据配置设置日志 log_config config.get(logging, {}) logging.basicConfig( levelgetattr(logging, log_config.get(level, INFO)), formatlog_config.get(format, %(asctime)s - %(levelname)s - %(message)s), handlers[ logging.FileHandler(log_config.get(file, spider.log)), logging.StreamHandler() # 同时输出到控制台 ] ) return logging.getLogger(__name__) def main(): # 1. 加载配置 with open(config.yaml, r, encodingutf-8) as f: config yaml.safe_load(f) logger setup_logging(config) # 2. 初始化核心组件 spider_config config[spider] downloader PoliteDownloader(default_delayspider_config.get(request_delay, 1.0)) if config.get(proxy, {}).get(enabled, False): downloader.proxy_list config[proxy][list] storage_config config[storage] if storage_config[type] csv: storage CSVStorage(storage_config[csv][file_path]) elif storage_config[type] mysql: storage DatabaseStorage(storage_config[database]) else: storage None logger.warning(未配置有效存储数据将不会被保存。) # 3. 初始化调度器并注入种子URL scheduler Scheduler(downloaderdownloader, parser_factoryParserFactory, storagestorage) for url in spider_config[start_urls]: scheduler.add_task(url, page_typelist_page) # 初始任务都是列表页 # 4. 启动爬虫设置停止条件 max_pages spider_config.get(max_pages, 100) logger.info(f爬虫启动最大抓取页数: {max_pages}) try: scheduler.run(max_tasksmax_pages) except KeyboardInterrupt: logger.info(用户中断爬虫运行。) finally: scheduler.shutdown() logger.info(爬虫运行结束。数据已保存。) if __name__ __main__: main()4.2 调度器与任务队列的实现核心调度器是串联整个流程的引擎。一个简单的内存中调度器可以这样实现import queue import threading from urllib.parse import urljoin, urlparse class Scheduler: def __init__(self, downloader, parser_factory, storage, max_workers5): self.downloader downloader self.parser_factory parser_factory self.storage storage self.task_queue queue.Queue() # 任务队列 self.visited_urls set() # 已访问URL集合用于去重 self.max_workers max_workers self.lock threading.Lock() # 用于visited_urls的线程安全 self.crawled_count 0 def add_task(self, url, page_typelist_page, depth0): 添加一个新任务到队列 with self.lock: if url in self.visited_urls: return False self.visited_urls.add(url) self.task_queue.put((url, page_type, depth)) return True def _worker(self): 工作线程函数 while True: try: url, page_type, depth self.task_queue.get(timeout5) # 超时退出 except queue.Empty: break # 队列为空且超时线程结束 try: self._process_task(url, page_type, depth) except Exception as e: logging.error(f处理任务 {url} 时发生错误: {e}) finally: self.task_queue.task_done() def _process_task(self, url, page_type, depth): 处理单个任务下载 - 解析 - 存储 - 生成新任务 logging.info(f正在处理 [{page_type}]: {url}) # 1. 下载 resp self.downloader.get_with_retry(url) if not resp or resp.status_code ! 200: logging.warning(f下载失败: {url}) return html resp.text # 2. 解析 try: parser self.parser_factory.get_parser(page_type, html) except ValueError as e: logging.error(f获取解析器失败: {e}) return if page_type list_page: # 列表页提取详情页链接和下一页链接 item_urls parser.extract_items() next_page_url parser.extract_next_page() base_domain f{urlparse(url).scheme}://{urlparse(url).netloc} for item_url in item_urls: full_item_url urljoin(base_domain, item_url) if not item_url.startswith(http) else item_url self.add_task(full_item_url, page_typedetail_page, depthdepth1) if next_page_url: full_next_url urljoin(base_domain, next_page_url) self.add_task(full_next_url, page_typelist_page, depthdepth1) elif page_type detail_page: # 详情页提取数据并存储 data parser.extract_detail() data[source_url] url # 记录来源 if self.storage: self.storage.save(data) self.crawled_count 1 logging.info(f已抓取 {self.crawled_count} 个详情页。最新数据: {data.get(title, N/A)}) def run(self, max_tasks1000): 启动调度器 threads [] for i in range(self.max_workers): t threading.Thread(targetself._worker, namefWorker-{i}) t.daemon True t.start() threads.append(t) # 等待所有任务完成或达到最大任务数 self.task_queue.join() # 注意这里 max_tasks 是通过初始种子URL和解析出的新URL数量间接控制的。 # 更精细的控制需要在 _process_task 中判断 self.crawled_count。 def shutdown(self): 关闭调度器执行清理工作 if self.storage: self.storage.close() logging.info(调度器已关闭。)5. 常见问题排查与实战经验分享即使框架设计得再完善在实际运行中还是会遇到各种问题。下面是我在多个类似项目中总结出的常见“坑”及其解决方案。5.1 反爬虫策略应对实录反爬现象可能原因排查与解决思路返回状态码403/404IP被封锁、请求头异常、Cookie失效。1.检查请求头确保User-Agent是有效的浏览器字符串并携带Referer、Accept-Language等常见头。2.检查会话如果网站需要登录确认Session或Cookie是否有效且未过期。3.使用代理立即切换新的代理IP并检查当前IP是否在黑名单中可通过访问https://httpbin.org/ip对比。4.降低频率大幅增加请求延迟模拟人类浏览行为。返回验证码页面请求行为被识别为机器人如频率过高、模式固定。1.立即暂停遇到验证码立刻停止对该域名的请求至少10-30分钟。2.分析触发条件检查是IP、账号还是行为模式触发的。尝试更换IP、清理Cookie重新开始。3.引入验证码识别对于简单图形验证码可考虑使用ddddocr、pytesseract等库自动识别需评估法律风险。复杂验证码如点选、滑块通常需要人工打码平台或更高级方案成本较高。根本策略优化爬虫行为使其更像真人。返回的数据是空或乱码网站动态加载JavaScript渲染、数据接口加密、编码问题。1.查看网页源码浏览器右键“查看网页源代码”搜索你想要的数据。如果源码里没有说明是JS动态加载的。2.寻找数据接口打开浏览器开发者工具F12的“网络”(Network)选项卡刷新页面筛选XHR或Fetch请求寻找返回JSON数据的API接口。直接请求这个接口会更简单高效。3.检查编码确保响应内容的编码如resp.encoding正确对于中文网站常为utf-8或gbk。4.使用渲染引擎对于纯JS渲染的页面如SPA应用需使用Selenium、Playwright或Pyppeteer等无头浏览器来获取完整HTML。请求超时或连接不稳定网络问题、目标服务器负载高、代理IP质量差。1.增加超时时间将timeout参数设得大一些如15-30秒。2.实现重试机制必须要有带指数退避的重试逻辑如前文代码所示。3.更换代理或网络环境。5.2 数据质量与存储的坑数据去重在将数据存入数据库前一定要根据业务逻辑去重。例如商品可以用唯一ID或“名称商家”的组合作为唯一键。可以在内存中用set记录已处理的ID或者在数据库层面设置UNIQUE约束使用INSERT IGNORE或ON DUPLICATE KEY UPDATE语句。增量抓取如何只抓取新的或更新的数据一个通用的策略是在存储数据时同时存储该数据的抓取时间和数据指纹如MD5哈希。下次抓取时先计算新数据的指纹与库中同一商品的最新指纹对比如果不同则更新。对于列表页可以记录最后抓取时间只请求此时间之后更新的页面如果网站支持按时间筛选。存储性能瓶颈频繁的数据库INSERT操作会成为瓶颈。解决方案是使用批量插入。例如在内存中积累100条或500条记录然后一次性执行INSERT INTO table VALUES (...), (...), ...语句效率能提升几十倍。对于文件存储也要避免每抓一条就打开、写入、关闭文件应保持文件打开状态或使用缓冲写入。5.3 工程化与维护心得配置化所有可能变化的参数URL、选择器、数据库连接、请求间隔一定要放到配置文件中。千万不要硬编码在代码里。日志是生命线日志级别要合理INFO记录正常流程如开始抓取XXXWARNING记录可恢复的错误如某个页面解析失败ERROR记录严重问题如数据库连接失败。日志要包含足够上下文如URL、任务ID方便定位问题。异常处理要细致每个可能失败的环节网络请求、解析、存储都要用try...except包裹记录错误并决定是重试、跳过还是终止任务。不要让一个页面的解析错误导致整个爬虫崩溃。尊重robots.txt使用urllib.robotparser模块来解析目标网站的robots.txt并遵守其中的规则。这是爬虫工程师的基本职业操守。考虑法律与伦理只抓取公开的、非敏感的数据。不要绕过付费墙不要抓取个人隐私信息。在数据使用前最好阅读一下网站的“服务条款”。最后像copaw-liexiaoyi这样的项目其最大价值在于提供了一个清晰的架构和一套可复用的模式。当你真正动手去实现它、并用自己的目标网站去适配时你会对HTTP协议、HTML结构、数据清洗、并发编程有更深刻的理解。我建议在跑通基础功能后可以尝试挑战一些进阶特性比如集成更智能的代理池管理、实现基于Redis的分布式爬虫、或者用Scrapy框架重写一遍来对比优劣。爬虫技术永无止境核心永远是在效率、稳定性和友好度之间找到最佳平衡点。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…