Python链家爬虫实战:从架构设计到反爬策略的完整指南 简介本资源是一套面向房地产数据分析初学者与行业研究者的Python链家二手房及租房数据爬虫实战源码解决房产市场原始数据获取难、结构化处理门槛高的实际问题适用于市场调研、投资分析、教学实践等场景。压缩包共20个文件含8个核心Python脚本如core.py主控逻辑、woaiwojialib.py定制化反爬交互、model.py数据建模、2个CSV数据文件houseinfo.csv与community.csv、2个PNG可视化图示、2个文本配置/日志文件以及Jupyter笔记本lianjia.ipynb用于交互式分析演示整体大小10.85MB。已有704人学习下载。读者可直接运行scrawl.py等模块完成链家网页数据抓取借助settings.py灵活配置参数通过jupyter笔记快速复现清洗、统计与可视化全流程目录结构分层清晰含独立工具库shlib.py、杂项函数misc.py与完整开源许可兼顾可读性、可维护性与合规性。1. 项目概述与核心价值最近在做一个数据分析项目需要大量城市二手房和租房市场的实时数据。找了一圈公开的数据源要么更新不及时要么字段不全要么就是收费昂贵。于是很自然地就想到了链家——这个国内最大的房产信息平台之一它上面的房源数据可以说是最全、最活跃的。手动去抄不现实一个城市每天新增的房源信息就成千上万条。所以自己动手写一个爬虫就成了最直接有效的解决方案。这个项目就是基于Python设计并实现一个针对链家二手房和租房信息的在线数据爬虫。它的核心目标很明确自动化、高效率、稳定地抓取链家网站上公开的房源列表页和详情页数据并将这些非结构化的网页信息转化为结构化的、可供分析的数据集。无论是想研究某个区域的房价走势、租金回报率还是想做个比价工具或者仅仅是学习网络爬虫技术这个项目都能提供一个从零到一的完整实战案例。我选择Python是因为它在数据抓取和处理领域的生态实在是太成熟了。requests负责网络请求BeautifulSoup或lxml负责解析HTMLpandas负责数据清洗和存储再加上一些用于控制爬取速度、处理异常的库几乎可以应对所有常见的爬虫场景。链家网站的结构相对清晰虽然有一定反爬措施但并非坚不可摧非常适合作为中级爬虫练手和实际应用的项目。注意任何爬虫项目都必须严格遵守目标网站的robots.txt协议并尊重网站的服务压力。本项目仅用于技术学习和研究所爬取的数据应为适度、低频的公开信息严禁用于商业爬取、恶意攻击或任何干扰网站正常运营的行为。2. 爬虫整体架构与设计思路设计一个健壮的爬虫不能一上来就写代码。我们需要先想清楚整个数据流转的流程以及每个环节可能遇到的问题。我的设计思路主要分为四个核心模块它们共同构成了爬虫的骨架。2.1 核心模块划分请求调度器 (Request Scheduler)这是爬虫的“大脑”。它负责生成需要爬取的URL队列。对于链家我们需要同时处理二手房和租房两个频道每个频道下又有不同的城市、区域、甚至排序方式。调度器要能灵活地配置这些起始点并管理URL的优先级与去重。网页下载器 (Downloader)这是爬虫的“双手”。它根据调度器给出的URL去实际访问网页并获取HTML内容。这里的关键在于模拟正常浏览器访问包括设置合理的请求头User-Agent、Referer等、处理Cookie、以及应对可能出现的反爬机制如IP限制、验证码。数据解析器 (Parser)这是爬虫的“眼睛”。下载下来的HTML是杂乱无章的解析器的任务就是从中精准地“抠”出我们需要的信息。比如房源标题、总价、单价、面积、户型、朝向、楼层、小区名、经纪人、发布时间等等。链家的页面结构会不时调整所以解析器的代码需要有一定的容错性和可维护性。数据存储器 (Storage)这是爬虫的“仓库”。解析出来的结构化数据需要被持久化保存。根据数据量和使用场景可以选择保存为CSV文件、JSON文件或者直接存入MySQL、MongoDB等数据库。我通常会先存为CSV方便快速查看和用Excel/Pandas处理如果数据量大或需要复杂查询再考虑导入数据库。2.2 技术栈选型与考量为什么选这些库每个选择背后都有实际考量requestsvsurllibrequests的API更加人性化会话保持、代理设置、超时处理都非常简洁社区活跃是Python HTTP客户端的首选。BeautifulSoup4vslxml这是一个经典选择。BeautifulSoup的语法更接近自然语言容错性好适合初学者和快速开发。lxml的解析速度极快适合处理大量页面。在本项目中由于链家页面结构不算特别复杂但页面量可能大我推荐使用lxml作为解析引擎因为速度优势在批量爬取时非常明显。我们可以用BeautifulSoup(html, ‘lxml’)的方式来结合两者的优点。pandas它不仅是数据分析神器其DataFrame对象也是临时存储和清洗数据的完美容器并且能一键导出为CSV/Excel极其方便。fake-useragent用于随机生成不同的浏览器User-Agent字符串是绕过基于User-Agent反爬的基础手段。time/random用于在请求间插入随机延时这是体现“礼貌爬虫”的关键能有效降低被封IP的风险。2.3 反爬策略初步分析在动手之前我们必须先“侦察”一下链家的反爬措施。通过手动浏览和简单测试我发现主要有以下几点请求头校验检查User-Agent没有或不常见的可能会被拒绝或返回简版页面。访问频率限制短时间内发起大量请求IP地址可能会被暂时封锁。Cookie/Session验证一些列表页可能需要携带特定的Cookie才能访问完整信息。页面结构动态化虽然链家主体内容仍是服务端渲染但部分数据可能通过接口获取需要分析XHR请求。针对这些我们的应对策略是伪装请求头使用fake-useragent库随机生成。控制爬取速度在每个请求之间设置随机间隔如2-5秒。使用会话利用requests.Session()保持连接和自动管理Cookie。优先分析列表页接口如果发现列表数据是通过Ajax加载的直接抓取接口数据会更高效、更稳定。3. 核心细节解析与实操要点明确了架构我们来深入每个环节的细节。这部分是爬虫稳定运行的关键很多坑都藏在这里。3.1 目标URL分析与构造链家二手房和租房的列表页URL有规律可循这是我们爬虫的入口。二手房列表页示例https://[城市拼音].lianjia.com/ershoufang/[区域拼音]/pg[页码]/例如北京海淀区的第二页https://bj.lianjia.com/ershoufang/haidian/pg2/我们可以通过拼接城市、区域、页码来批量生成URL。租房列表页示例https://[城市拼音].lianjia.com/zufang/[区域拼音]/pg[页码]/详情页URL 通常可以在列表页中每个房源卡片里找到href属性是相对路径需要拼接基础域名。实操心得不要硬编码URL模式。最好将城市、区域的映射关系如中文名到拼音保存在一个配置字典或JSON文件中这样爬虫更容易扩展和维护。另外链家可能会对URL进行小幅调整定期检查是必要的。3.2 网页下载器的稳健性实现一个健壮的下载器不能只会发请求。以下是核心代码框架和要点import requests from fake_useragent import UserAgent import time import random class LianJiaDownloader: def __init__(self): self.session requests.Session() self.ua UserAgent() # 设置一个基础的请求头模板 self.base_headers { Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9, Accept-Encoding: gzip, deflate, br, Connection: keep-alive, } self.request_count 0 def get_page(self, url, max_retries3): 下载页面包含重试机制 for i in range(max_retries): try: # 每次请求使用随机User-Agent headers self.base_headers.copy() headers[User-Agent] self.ua.random # 重要添加Referer模拟从链家站内跳转 headers[Referer] https://www.lianjia.com/ response self.session.get(url, headersheaders, timeout10) response.raise_for_status() # 检查HTTP状态码非200会抛出异常 # 检查返回内容是否有效有时被封会返回空页面或错误信息 if len(response.content) 5000: # 简单通过长度判断阈值可根据实际情况调整 print(f警告页面内容过短可能被拦截。URL: {url}) return None if response.encoding is None: response.encoding utf-8 # 确保编码正确 self.request_count 1 # 礼貌性延迟并加入随机性 time.sleep(random.uniform(2, 4)) return response.text except requests.exceptions.RequestException as e: print(f请求失败 (尝试 {i1}/{max_retries}): {url}, 错误: {e}) if i max_retries - 1: sleep_time random.uniform(5, 10) # 失败后等待更久 print(f等待 {sleep_time:.2f} 秒后重试...) time.sleep(sleep_time) else: print(fURL {url} 重试{max_retries}次后仍失败。) return None return None关键点解析使用Sessionself.session可以自动处理Cookie保持多次请求间的状态比单次requests.get更接近真实浏览器行为。随机User-AgentUserAgent().random每次生成一个主流浏览器的UA避免因单一UA被识别。设置Referer这是一个容易被忽略但很重要的反爬点。告诉服务器你是从链家站内跳转过来的更像真人行为。异常处理与重试网络请求充满不确定性。try...except块捕获超时、连接错误等异常并实现重试逻辑提高爬虫的鲁棒性。礼貌延迟time.sleep(random.uniform(2, 4))是爬虫的“道德底线”。固定延时容易被识别随机延时更安全。这个时间间隔需要根据目标网站的承受能力和自身需求权衡。内容有效性检查简单的长度检查可以快速过滤掉因被反爬而返回的错误页面或空页面。3.3 数据解析器的精准定位解析是爬虫中最“脏”最累的活因为你要面对不断变化的HTML结构。我们的原则是使用属性组合定位避免使用易变的类名或结构索引。以链家二手房详情页为例我们需要提取房源标题、总价等。首先要用浏览器的开发者工具F12仔细分析元素。from bs4 import BeautifulSoup import re class LianJiaParser: staticmethod def parse_list_page(html): 解析列表页获取房源链接和基本信息 if not html: return [] soup BeautifulSoup(html, lxml) house_list [] # 定位房源列表项通常有特定的class items soup.find_all(li, class_re.compile(clear LOGCLICKDATA)) for item in items: house_info {} # 提取详情页链接 link_tag item.find(a, class_img) if link_tag and link_tag.get(href): house_info[detail_url] https://bj.lianjia.com link_tag[href] # 提取标题 title_tag item.find(div, class_title) if title_tag: house_info[title] title_tag.text.strip() # 提取价格等信息 - 这里需要更精细的定位 price_tag item.find(div, class_totalPrice) if price_tag: house_info[total_price] price_tag.text.strip() # ... 提取其他列表页可见信息如小区、户型、面积等 house_list.append(house_info) return house_list staticmethod def parse_detail_page(html): 解析详情页获取完整信息 if not html: return {} soup BeautifulSoup(html, lxml) detail_info {} # 1. 提取标题 title_tag soup.find(h1, class_main) detail_info[title] title_tag.text.strip() if title_tag else # 2. 提取总价和单价 - 通常在一个class为price的div里 price_div soup.find(div, class_price) if price_div: total_price_span price_div.find(span, class_total) unit_price_span price_div.find(span, class_unitPriceValue) detail_info[total_price] total_price_span.text.strip() if total_price_span else detail_info[unit_price] unit_price_span.text.strip() if unit_price_span else # 3. 提取房源基本信息面积、户型、楼层等- 通常在class为introContent的div里 intro_div soup.find(div, class_introContent) if intro_div: base_items intro_div.find_all(li) for li in base_items: text li.text.strip() if 房屋户型 in text: detail_info[layout] text.split()[-1] elif 建筑面积 in text: detail_info[area] text.split()[-1] elif 所在楼层 in text: detail_info[floor] text.split()[-1] # ... 同理提取其他字段 # 4. 提取小区信息 community_tag soup.find(div, class_communityName) if community_tag: a_tag community_tag.find(a) detail_info[community] a_tag.text.strip() if a_tag else # 5. 提取经纪人信息 agent_tag soup.find(div, class_brokerInfoText) if agent_tag: detail_info[agent] agent_tag.text.strip() # 6. 提取房源标签 tags [] tag_div soup.find(div, class_tags) if tag_div: tags [span.text.strip() for span in tag_div.find_all(span)] detail_info[tags] ,.join(tags) return detail_info解析技巧与避坑指南使用正则表达式匹配类名class_re.compile(clear LOGCLICKDATA)可以匹配类名中包含该字符串的元素即使类名有增减如clear LOGCLICKDATA noresult也能匹配到比精确匹配更健壮。层层防御每次使用.find()或.find_all()后都要判断结果是否为None避免后续调用.text或[‘href’]时抛出AttributeError。组合定位不要只依赖一个类名或标签。例如找价格先找到特征明显的父元素class’price’的div再在其内部寻找具体的价格标签这样即使内部结构微调也不容易出错。文本清洗.text.strip()是基本操作但链家的数据里可能包含空格、换行符、特殊字符。有时需要用re.sub(‘\s’, ‘ ‘, text)来合并多余空白。应对结构变化链家前端的类名和结构可能会改版。最稳妥的方法是寻找具有唯一性的>import pandas as pd from downloader import LianJiaDownloader from parser import LianJiaParser import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s: %(message)s) class LianJiaCrawler: def __init__(self, start_urls, max_pages_per_area100): self.downloader LianJiaDownloader() self.parser LianJiaParser() self.start_urls start_urls # 起始URL列表例如不同区域的列表页第一页 self.max_pages max_pages_per_area self.all_house_data [] # 临时存储所有房源数据 def crawl(self): 主爬取流程 for start_url in self.start_urls: logging.info(f开始爬取区域: {start_url}) current_page 1 while current_page self.max_pages: # 1. 构造列表页URL list_url self._generate_list_url(start_url, current_page) if not list_url: break logging.info(f爬取列表页: {list_url}) # 2. 下载列表页 list_html self.downloader.get_page(list_url) if not list_html: logging.warning(f列表页下载失败可能已无更多页面或触发反爬。URL: {list_url}) break # 如果列表页都下载失败可能该区域爬完了或IP受限跳出循环 # 3. 解析列表页获取详情页链接 house_previews self.parser.parse_list_page(list_html) if not house_previews: logging.info(f列表页 {list_url} 未解析到房源可能已到末页。) break # 当前页没有房源说明已爬完所有页面 # 4. 遍历每个房源爬取详情页 for preview in house_previews: detail_url preview.get(detail_url) if not detail_url: continue # 可选检查该详情页是否已爬取过去重 # if self._is_duplicate(detail_url): # continue logging.info(f爬取详情页: {detail_url}) detail_html self.downloader.get_page(detail_url) if detail_html: detail_data self.parser.parse_detail_page(detail_html) # 将列表页的部分信息合并进来 detail_data.update({k: v for k, v in preview.items() if k ! detail_url}) detail_data[source_url] detail_url # 记录来源 detail_data[crawl_time] pd.Timestamp.now() # 记录爬取时间 self.all_house_data.append(detail_data) # 每爬取N条保存一次防止数据丢失 if len(self.all_house_data) % 20 0: self._save_to_csv() else: logging.warning(f详情页下载失败: {detail_url}) # 详情页之间的延时已在downloader中实现 # 5. 判断是否还有下一页可选更智能的方式 # 可以通过解析列表页的“下一页”按钮是否存在来判断 # 这里我们使用简单的页码递增配合“解析不到房源即停止”的机制 current_page 1 # 一个区域爬取完成后保存一次数据 self._save_to_csv() logging.info(所有区域爬取完成) def _generate_list_url(self, base_url, page): 根据基础URL和页码生成列表页URL # 假设base_url是类似 https://bj.lianjia.com/ershoufang/haidian/ 的形式 if page 1: return base_url else: # 确保base_url以/结尾 base base_url.rstrip(/) return f{base}/pg{page}/ def _save_to_csv(self, filenamelianjia_houses.csv): 将数据保存到CSV文件 if not self.all_house_data: return df pd.DataFrame(self.all_house_data) # 如果文件已存在则追加写入不包含表头 try: existing_df pd.read_csv(filename) df.to_csv(filename, modea, headerFalse, indexFalse, encodingutf-8-sig) except FileNotFoundError: # 文件不存在首次写入包含表头 df.to_csv(filename, indexFalse, encodingutf-8-sig) logging.info(f数据已保存至 {filename}当前累计 {len(self.all_house_data)} 条记录。) # 清空临时列表避免内存占用过大如果数据量极大 # self.all_house_data.clear()4.2 数据存储与持久化方案上面代码中使用了CSV文件进行存储这是最简单直接的方式。pandas的to_csv方法非常强大encoding’utf-8-sig’可以确保用Excel打开时中文不乱码。对于更复杂的场景可以考虑以下方案数据库存储 (推荐用于大规模数据)SQLite轻量级无需安装服务器适合单机爬虫。Python内置sqlite3模块。MySQL/PostgreSQL功能强大适合多机协作或需要复杂查询的场景。可以使用pymysql或psycopg2库。MongoDB文档型数据库schema灵活非常适合存储结构可能变化的爬虫数据。使用pymongo库。增量爬取与去重 爬虫不应该每次都从头爬。我们可以在数据库中为每条数据增加一个唯一标识如详情页URL的MD5值并在爬取前先查询该标识是否存在。也可以记录每条数据的爬取时间方便后续只爬取更新过的数据。数据清洗与标准化 爬取下来的原始数据往往是“脏”的。在存储前或存储后需要进行清洗价格字段去除“万”、“元/平米”等字符转换为数值类型float。面积字段提取数字部分。户型字段拆分为室、厅、卫等独立字段。楼层字段分离出总楼层和当前楼层。处理缺失值对于解析失败或页面没有的字段填充为None或空字符串。这部分工作可以在Parser类中做也可以在存储到DataFrame后用pandas的向量化操作批量处理效率更高。5. 高级策略与性能优化当基本爬虫跑通后我们可能会面临效率瓶颈和更严格的反爬。这时就需要引入一些高级策略。5.1 并发爬取提升效率单线程爬取速度太慢。我们可以使用concurrent.futures库的ThreadPoolExecutor来实现多线程加速详情页的爬取。from concurrent.futures import ThreadPoolExecutor, as_completed class LianJiaCrawlerConcurrent(LianJiaCrawler): def crawl_details_concurrently(self, detail_urls, max_workers5): 并发爬取一批详情页 with ThreadPoolExecutor(max_workersmax_workers) as executor: # 提交任务 future_to_url {executor.submit(self._crawl_single_detail, url): url for url in detail_urls} # 获取结果 for future in as_completed(future_to_url): url future_to_url[future] try: detail_data future.result() if detail_data: self.all_house_data.append(detail_data) except Exception as exc: logging.error(f详情页 {url} 爬取过程中产生异常: {exc}) self._save_to_csv() def _crawl_single_detail(self, detail_url): 包装单页爬取逻辑供线程池调用 detail_html self.downloader.get_page(detail_url) if detail_html: return self.parser.parse_detail_page(detail_html) return None重要提醒并发爬取是一把双刃剑。优点极大提升爬取速度。缺点对目标网站压力剧增极易触发反爬机制IP封禁、请求限制。同时请求间隔难以控制。建议务必谨慎使用。即使使用并发也必须在Downloader的get_page方法中保留全局性的、线程安全的延时控制这需要更复杂的锁机制或者使用更高级的限速队列。对于链家这类商业网站强烈不建议开启过高并发如超过3个线程礼貌爬取是第一原则。5.2 应对更复杂的反爬机制如果基础的伪装和延时仍然导致频繁被封可能需要考虑以下方案代理IP池这是应对IP封锁最有效的方法。你需要一个稳定的代理IP来源付费服务通常更可靠。在requests.get中通过proxies参数设置代理。proxies { http: http://your-proxy-ip:port, https: https://your-proxy-ip:port, } response self.session.get(url, headersheaders, proxiesproxies, timeout10)需要实现一个代理IP管理器负责IP的获取、验证、失效剔除和轮换。浏览器自动化工具对于JavaScript渲染严重或反爬极强的网站requests可能无法获取到有效内容。这时可以考虑Selenium或Playwright。它们能控制真实浏览器可以执行JS但速度慢、资源消耗大应作为最后手段。from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC options webdriver.ChromeOptions() options.add_argument(--headless) # 无头模式不显示浏览器窗口 driver webdriver.Chrome(optionsoptions) driver.get(url) # 等待特定元素加载出来 element WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, main)) ) html driver.page_source driver.quit() # 然后用BeautifulSoup解析html模拟登录如果需要爬取“我的关注”等需要登录后才能查看的数据就需要模拟登录。这通常涉及分析登录接口的POST请求处理可能存在的Token、验证码等。成功登录后使用Session对象保持登录状态即可爬取受限页面。6. 常见问题与排查技巧实录在实际操作中你一定会遇到各种各样的问题。下面是我踩过的一些坑和解决方法。6.1 问题排查清单问题现象可能原因排查步骤与解决方案返回状态码403IP被封锁、请求头不完整、Cookie无效。1. 检查User-Agent,Referer是否设置且合理。2. 暂停爬虫等待一段时间如半小时再试。3. 更换代理IP。4. 检查Session是否保持了必要的Cookie可尝试手动浏览器登录后复制Cookie。返回内容为空或极短触发了反爬返回了错误页或验证页。1. 打印返回的HTML内容查看是否是验证码页面或提示“访问过于频繁”的页面。2. 大幅增加请求间隔时间如10-30秒。3. 检查请求参数是否完整某些页面可能需要特定的查询参数。解析不到数据但浏览器能看到页面结构已更新或数据由JavaScript动态加载。1. 使用浏览器开发者工具对比当前爬取的HTML和浏览器中“查看网页源代码”的HTML看目标数据是否存在。2. 如果不存在说明是JS加载。需要分析网络请求找到真正的数据接口通常是XHR请求直接爬取接口URL通常是JSON格式。3. 如果必须从渲染后页面获取则需使用Selenium。数据字段错乱或缺失解析规则写得太“死”无法适应页面微小变化。1. 使用更宽松的定位方式如re.compile匹配部分类名。2. 增加更多的try...except块对每个字段单独处理避免一个解析失败导致整个条目丢失。3. 定期运行测试用例确保解析器对新页面依然有效。爬取速度越来越慢最后停止可能触发了网站的滑动验证码或更复杂的反爬策略。1. 这是最棘手的情况。首先检查是否是IP被彻底封禁换一个网络环境试试。2. 考虑引入更复杂的策略降低并发数至1使用高质量住宅代理IP模拟更真实的人类行为如随机滑动鼠标、随机浏览时间。3.评估成本与收益对于链家如果只是学习或小规模研究遵守规则、低速爬取是唯一可持续的方式。大规模商业爬取面临极高的法律和技术风险。6.2 调试与日志记录良好的日志是调试爬虫的生命线。不要只用print。import logging import sys # 配置日志同时输出到控制台和文件 logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s, handlers[ logging.FileHandler(crawler.log, encodingutf-8), logging.StreamHandler(sys.stdout) ] ) logger logging.getLogger(__name__) # 在代码中不同级别记录 logger.debug(f正在解析URL: {url}) # 详细信息调试用 logger.info(f成功爬取列表页共找到 {len(items)} 个房源。) logger.warning(f页面内容长度异常: {len(html)}) logger.error(f请求失败: {url}, 错误: {e})运行爬虫时打开crawler.log文件结合控制台输出可以清晰地看到爬虫的运行状态、成功和失败记录方便定位问题。6.3 代码维护与可持续性配置化将城市列表、区域列表、请求头模板、数据库连接信息、爬取间隔等参数提取到配置文件如config.yaml或config.py中避免硬编码。模块化正如本项目设计将下载、解析、存储、调度分离成独立模块方便单独测试和替换。例如更换解析引擎或存储后端时只需修改对应模块。异常处理网络爬虫运行在复杂的环境中必须假设一切外部调用都可能失败。为每一个网络请求、文件操作、数据库操作都加上异常处理确保单点失败不会导致整个程序崩溃。尊重robots.txt在爬取前访问https://[城市].lianjia.com/robots.txt查看网站允许和禁止爬取的路径。这是一个法律和道德上的最佳实践。这个基于Python的链家爬虫项目从设计到实现涉及了网络请求、HTML解析、数据清洗、并发控制、反爬应对等多个知识点。它不是一个一劳永逸的工具而是一个需要根据目标网站变化而持续维护的系统。最宝贵的收获不是最终的数据集而是在解决一个个具体问题过程中积累的经验和对HTTP协议、Web技术的更深理解。记住爬虫的世界里稳健和礼貌远比速度重要。本文还有配套的精品资源点击获取