如何快速搭建实用的爬虫管理平台

MarvinZhang · · 3216 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

本次篇文章内容较多,涉及知识较广,读完需要大约 20 分钟,请读者耐心阅读。

前言

大多数企业都离不开爬虫,爬虫是获取数据的一种有效方式。对搜索引擎来说,爬虫不可或缺;对舆情公司来说,爬虫是基础;对 NLP来说,爬虫可以获取语料;对初创公司来说,爬虫可以获取初始内容。但是爬虫技术纷繁复杂,不同类型的抓取场景会运用到不同的技术。例如,简单的静态页面可以用 HTTP 请求+HTML 解析器直接搞定;一个动态页面需要用 Puppeteer 或 Selenium等自动化测试工具;有反爬的网站需要用到代理、打码等技术;等等。因此,对爬虫有规模量级要求的企业或个人需要同时处理不同类别的爬虫,这会凭空增添很多附加的管理成本。同时,爬虫管理者还需要应对网站内容变更、持续增量抓取、任务失败等问题。因此一个成熟的爬虫管理流程应该包含一个管理系统,能够有效处理上述问题。

理解什么是爬虫管理平台

定义

爬虫管理平台是一个一站式管理系统,集爬虫部署、任务调度、任务监控、结果展示等模块于一体,通常配有可视化 UI 界面,可以在 Web 端通过与 UI 界面交互来有效管理爬虫。爬虫管理平台一般来说是支持分布式的,可以在多台机器上协作运行。

当然,上述这个定义是狭义的,通常针对于技术人员或开发者或技术经理。企业内部一般都会开发自己的内部爬虫管理系统,以应对复杂的爬虫管理需求。这样的系统就是上述定义的狭义的爬虫管理平台。

广义爬虫管理平台

而什么是广义的爬虫管理平台呢?您可能听说过神箭手(后转型为后羿采集器)和八爪鱼吧。前者是基于云服务的,可以在线上编写、运行和监控爬虫,在广义爬虫平台中最接近狭义定义的爬虫管理平台;后者是一个大众的商业爬虫抓取工具,可以让小白用户拖拉拽编写、运行爬虫,导出数据。您也可能见过各种 API 聚合服务商,例如聚合数据,这是一个可以直接调用网站接口获取数据的平台,这其实也算作爬虫平台的一个变种,只是它帮你完成了爬虫编写这一过程。而介于这两者之间的呢,国外有一家叫 Kimonolab 的公司,它开发了一个叫 Kimono 的 Chrome 插件,可以让用户在页面上可视化的点击元素并生成抓取规则,并在其网站上生成爬虫程序,用户提交任务,后台就可以自动在网站上抓取数据了。Kimono 是一个伟大的爬虫应用,但可惜的是,Kimonolab 已经被大数据公司 Plantir 收购,现在也就无法体验了。

在本文中,我们主要关注狭义定义的爬虫管理平台,因此后面所讲到的爬虫管理平台都是指狭义的定义。

爬虫管理平台模块

以下是一个典型的爬虫管理平台所涉及的模块。

爬虫管理平台架构

典型爬虫管理平台的模块主要包含以下内容:

  • 任务管理:如何执行、调度爬虫抓取任务,以及如何监控任务,包括日志监控等等;
  • 爬虫管理:包括爬虫部署,即将开发好的爬虫部署(打包或复制)到相应的节点上,以及爬虫配置和版本管理;
  • 节点管理:包括节点(服务器/机器)的注册和监控,以及节点之间的通信,如何监控节点性能状况等;
  • 前端应用:包括一个可视化 UI 界面,让用户可通过与其交互,与后台应用进行通信。

当然,有些爬虫管理平台可能还不止这些模块,它可能包括其他比较实用的功能,例如可配置的抓取规则、可视化配置抓取规则、代理池、Cookie 池、异常监控等等。

为什么需要爬虫管理平台

有了爬虫管理平台,开发者特别是爬虫工程师就能够方便的添加爬虫、执行任务、查看结果,而不用在命令行之间来回切换,非常容易出错。一个常见的场景就是爬虫工程师最初技术选型用了 scrapy 和 crontab 来管理爬虫任务,他不得不小心翼翼的选择定时任务的时间区间,以至于不会将服务器 CPU 或内存占满;更棘手的问题是,他还需要将 scrapy 产生的日志存到文件里,一旦爬虫出错了,他不得不用 shell 命令一个一个来查看日志来定位错误原因,严重时会花上一个整天;还有个严重的问题,爬虫工程师可能发现公司业务量在增加,他需要写上百个爬虫来满足公司的业务需求,而用 scrapy 和 crontab 来管理完全就是个噩梦。可怜的爬虫工程师其实完全可以选择一个合适爬虫管理平台来解决他的问题。

如何选择一个合适的爬虫管理平台

当您愿意解决前面提到的爬虫工程师遇到的困难问题,而转而想选择一个合适的爬虫管理平台时。

您首先应该回答的问题是:我们是否需要从零开始开发一套系统(Start from scratch)?要回答这个问题,您应该先回答下面几个问题:

  1. 我们的需求是否复杂到需要完全定制化开发一套新系统(例如要求复杂的权限管理)?
  2. 我们的团队是否有足够的技术实力来开发这套系统(例如有经验丰富的前后端开发工程师)?
  3. 我们的时间资源是否足够我们开发这套系统(例如项目计划周期为一年)?

如果上述三个问题的答案任意一个为“否”,您应该好好考虑利用市面上已有的开源爬虫管理平台来满足您的需求。

以下为市面上已有的开源爬虫管理平台:

平台名称 技术 优点 缺点
SpiderKeeper Python Flask 基于 scrapyd,开源版 Scrapyhub,非常简洁的 UI 界面,支持定时任务 可能有些过于简洁了,不支持分页,不支持节点管理,不支持 scrapy 以外的爬虫
Gerapy Python Django + Vue Gerapy 是崔庆才大神开发的爬虫管理平台,安装部署非常简单,同样基于 scrapyd,有精美的 UI 界面,支持节点管理、代码编辑、可配置规则等功能 同样不支持 scrapy 以外的爬虫,而且据使用者反馈,1.0 版本有很多 bug,期待 2.0 版本会有一定程度的改进
Scrapydweb Python Flask + Vue 精美的 UI 界面,内置了 scrapy 日志解析器,有较多任务运行统计图表,支持节点管理、定时任务、邮件提醒、移动界面,算是 scrapy-based 中功能完善的爬虫管理平台 同样不支持 scrapy 以外的爬虫,Python Flask 为后端,性能上有一定局限性 Python Flask + Vue
Crawlab Golang + Vue 不局限于 scrapy,可以运行任何语言和框架的爬虫,精美的 UI 界面,天然支持分布式爬虫,支持节点管理、爬虫管理、任务管理、定时任务、结果导出、数据统计等功能 部署稍微有一些麻烦(不过利用 Docker 可以一键部署),最新版本暂时不支持可配置爬虫

总的来说,SpiderKeeper 可能是最早的爬虫管理平台,但功能相对来说比较局限;Gerapy 虽然功能齐全,界面精美,但有不少 bug 需要处理,建议有需求的用户等待 2.0 版本;Scrapydweb是一个比较完善的爬虫管理平台,不过和前两者一样,都是基于 scrapyd 的,因此只能运行 scrapy 爬虫;而Crawlab是一个非常灵活的爬虫管理平台,可以运行 Python、Nodejs、Java、PHP、Go 写的爬虫,而且功能比较齐全,只是部署起来相对于前三者来说要麻烦一些,不过对于 Docker 使用者来说可以做到一件部署(后面我们会讲)。

因此,对于重度 scrapy 爬虫依赖的、又不想折腾的开发者,可以考虑 Scrapydweb;而对于有各种类型的、复杂技术结构的爬虫开发者来说,应该优先考虑更灵活的 Crawlab。当然,不是说 Crawlab 对 scrapy 支持不友好,Crawlab 同样可以很好的集成 scrapy,后面会介绍。

作为 Crawlab 的作者,不想王婆卖瓜,自卖自夸,作者仅仅希望将最好的技术选型推荐给开发者,让开发者根据自身的需求来决定该使用哪种爬虫管理平台。

爬虫管理平台 Crawlab 介绍

简介

Crawlab 是基于 Golang 的分布式爬虫管理平台,支持 Python、NodeJS、Java、Go、PHP 等多种编程语言以及多种爬虫框架。

Crawlab 自今年三月份上线以来受到爬虫爱好者们和开发者们的好评,不少使用者还表示会用 Crawlab 搭建公司的爬虫平台。经过近数月的迭代,Crawlab 陆续上线了定时任务、数据分析、网站信息、可配置爬虫、自动提取字段、下载结果、上传爬虫等功能,将平台变得得更加实用,更加全面,能够真正帮助用户解决爬虫管理困难的问题。如今在 Github 上有近 1k 的 star,相关社区(微信群、微信公众号)也建立起来,四分之一的用户表示已经将 Crawlab 应用于企业爬虫管理。可以看出,Crawlab 是受开发者们关注和喜欢的。

解决问题

Crawlab 主要解决的是大量爬虫管理困难的问题,例如需要监控上百个网站的参杂 scrapy 和 selenium 的项目不容易做到同时管理,而且命令行管理的成本非常高,还容易出错。Crawlab 支持任何语言和任何框架,配合任务调度、任务监控,很容易做到对成规模的爬虫项目进行有效监控管理。

界面及使用

下面是 Crawlab 爬虫列表页面的截图。

Crawlab 爬虫列表

用户只需要将爬虫上传到 Crawlab,配置执行命令,点击“运行”按钮,就可以执行爬虫任务了。爬虫任务可以在任何节点上运行。从上图可以看到,Crawlab 有节点管理、爬虫管理、任务管理、定时任务、用户管理等模块。

整体架构

以下是 Crawlab 的整体架构图,由五大部分组成:

  1. 主节点(Master Node):负责任务派发、API、部署爬虫等;
  2. 工作节点(Worker Node):负责执行爬虫任务;
  3. MongoDB 数据库:存储节点、爬虫、任务等日常运行数据;
  4. Redis 数据库:储存任务消息队列、节点心跳等信息。
  5. 前端客户端:Vue 应用,负责前端交互和向后端请求数据。

Crawlab 架构

关于 Crawlab 如何使用和详细原理超出了本篇文章的范围,感兴趣的可以去参考Github 主页相关文档

使用 Docker 部署安装 Crawlab

Docker 镜像

Docker 是部署 Crawlab 最方便和简洁的方式。其他部署方式包括直接部署,不过对于想快速搭建平台的开发者来说不推荐。Crawlab 已在Dockerhub上注册了相关的镜像,开发者仅需要执行docker pull tikazyq/crawlab命令就可以将 Crawlab 的镜像下载下来。

读者可以去 Dockerhub 上查看 Crawlab 的镜像,只有仅不到 300Mb。地址:https://hub.docker.com/r/tika...

Dockerhub Page

安装 Docker

要使用 Docker 来部署 Crawlab,您首先得保证 Docker 已经安装好。请参考以下文档来安装。

操作系统 文档
Mac https://docs.docker.com/docke...
Windows https://docs.docker.com/docke...
Ubuntu https://docs.docker.com/insta...
Debian https://docs.docker.com/insta...
CentOS https://docs.docker.com/insta...
Fedora https://docs.docker.com/insta...
其他 Linux 发行版 https://docs.docker.com/insta...

安装 Docker Compose

Docker Compose 是简单的运行 Docker 集群的工具,非常轻量级,我们将用到 Docker Compose 来一键部署 Crawlab。

Docker 的官方网站已经有如何安装 Docker Compose 的教程,点击链接查看。这里简单介绍一下。

操作系统 安装步骤
Mac Docker Desktop for Mac 或 Docker Toolbox 自带,不用单独安装
Windows Docker Desktop for Windows 或 Docker Toolbox 自带,不用单独安装
Linux 参考表格下面的命令
其他选择 通过pip安装,pip install docker-compose,如果没有virtualenv,需要用sudo

Linux 用户请用以下命令安装。

# 下载 docker-compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose`

# 将 docker-compose 变成执行文件
sudo chmod +x /usr/local/bin/docker-compose

拉取镜像

在拉取镜像之前,您需要配置一下镜像源。因为在国内,使用原有的镜像源速度不是很快,需要使用 DockerHub 在国内的加速器。请创建/etc/docker/daemon.json文件,输入如下内容。

{
  "registry-mirrors": ["https://registry.docker-cn.com"]
}

然后拉取镜像,就会快很多了。当然,您也可以用其他镜像源,可以网上去搜索一下。执行以下命令将 Crawlab 镜像拉取下来。

docker pull tikazyq/crawlab:latest

下图为拉取镜像时的命令行界面。

docker pull

启动 Crawlab

我们将用 Docker Compose 启动 Crawlab 以及其依赖的数据库 MongoDB 和 Redis。首先我们需要修改一下 Docker Compose 的 yaml 配置文件docker-compose.yml。这个配置文件定义了需要启动的容器服务(Container Services)以及网络配置(Network Configuration)。这里我们用 Crawlab 自带的docker-compose.yml

version: '3.3'  # Docker Compose 的版本号(请看后续说明)
services:  # 服务
  master:  # 服务名称
    image: tikazyq/crawlab:latest  # 服务对应的镜像名称
    container_name: master  # 服务对应的容器名称
    environment:  # 这里定义传入的环境变量
      CRAWLAB_API_ADDRESS: "localhost:8000"  # 前端调用的 API 地址,默认为 localhost:8000
      CRAWLAB_SERVER_MASTER: "Y"  # 是否为主节点,Y/N
      CRAWLAB_MONGO_HOST: "mongo"  # MongoDB host,由于在 Docker Compose 里,可以引用服务名称
      CRAWLAB_REDIS_ADDRESS: "redis"  # Redis host,由于在 Docker Compose 里,可以引用服务名称
    ports:  # 映射的端口
      - "8080:8080" # 前端端口
      - "8000:8000" # 后端端口
    depends_on: # 依赖的服务
      - mongo  # MongoDB
      - redis  # Redis
  worker:  # 工作节点,与主节点配置类似,不重复写了
    image: tikazyq/crawlab:latest
    container_name: worker
    environment:
      CRAWLAB_SERVER_MASTER: "N"
      CRAWLAB_MONGO_HOST: "mongo"
      CRAWLAB_REDIS_ADDRESS: "redis"
    depends_on:
      - mongo
      - redis
  mongo:  # MongoDB 服务名称
    image: mongo:latest  # MongoDB 镜像名称
    restart: always  # 重启策略为“总是”
    ports:  # 映射端口
      - "27017:27017"
  redis:  # Redis 服务名称
    image: redis:latest  # Redis 镜像名称
    restart: always  # 重启策略为“总是”
    ports:  # 映射端口
      - "6379:6379"

读者可以根据自己的要求来配置docker-compose.yml。尤其需要注意CRAWLAB_API_ADDRESS这个环境变量,很多初学使用者都是因为该变量配置不正确而导致无法登陆。大多数情况,您不用做任何配置更改。请参考Q&A来处理常见问题,以及详细的环境变量配置文档来帮助根据自身环境配置 Crawlab。

然后,运行下列命令启动 Crawlab。可以加一个-d参数让 Docker Compose 后台运行。

docker-compose up

运行上述命令后,Docker Compose 会去拉取 MongoDB 和 Redis 的镜像,这可能会花几分钟时间。拉取完毕后,四个服务会依次启动,您将会在命令行中看到如下内容。

docker-compose

正常情况下,您应该可以看到四个服务都启动成功,并能够顺利打印日志。如果启动不成功,请微信联系作者(tikazyq1)或在 Github 上提 Issue。

如果您是在本机上启动的 Docker Compose,可以在浏览器中输入http://localhost:8080,然后就能看到登陆界面了;如果您是在其他机器上启动的 Docker Compose,您需要在浏览器中输入http://<your_ip>:8080来看到登陆界面,<your_ip>是其他机器的 IP 地址(请保证 8080 端口在该机器已对外开放)。

login

初始登陆用户名密码是 admin/admin,您可以使用这个用户名密码来登陆。如果您的环境变量CRAWLAB_API_ADDRESS设置得不正确,您可能会看到点击登陆后登陆按钮会一直转圈而没有任何提示。这时请重新在docker-compose.yml中设置正确的CRAWLAB_API_ADDRESS(将localhost替换为<your_ip>),重新启动docker-compose up。然后在浏览器中输入http://<your_ip>:8080

登陆之后您将看到 Crawlab 的主页。

home

本篇文章主要介绍如何搭建爬虫管理平台 Crawlab,因此不会详细介绍如何使用 Crawlab(可能会创建另一篇文章来详细介绍,有兴趣者可以关注一下)。如果您有困惑,请查看相关文档来了解如何使用。同时,您也可以加作者微信(tikazyq1)并注明 Crawlab,作者将将您拉入讨论群,您可以在那里答疑解惑。

如何将 Scrapy 等爬虫集成到 Crawlab

众所周知,Scrapy 是非常受欢迎的爬虫框架,其灵活的框架设计、高并发、易用性以及可扩展性让很多开发者和企业大量采用。市面上的爬虫管理平台几乎都支持 Scrapy 爬虫,Crawlab 也不例外,但 Crawlab 可以运行 puppeteer、selenium 等其他爬虫。下面将介绍一下在 Crawlab 中如何运行 scrapy 爬虫。

Crawlab 是执行爬虫基本原理

Crawlab 执行爬虫的原理很简单,其实就是一个 shell 命令。用户在爬虫中输入执行爬虫的 shell 命令,例如scrapy crawl some_spider,Crawlab 执行器会读取这个命令,并在 shell 中直接执行。因此,每一次运行爬虫任务,就是执行了一次 shell 命令(当然,实际情况要比这个复杂很多,感兴趣的可以去参考官方文档)。Crawlab 是支持展示和导出爬虫结果的,不过这需要稍微多做一些工作。

编写 Pipeline

要集成 scrapy 爬虫,无非就是将爬虫抓取的数据存到 Crawlab 的数据库里,然后用任务 ID 关联起来。每次执行爬虫任务,任务 ID 会通过环境变量传到爬虫程序中,因此我们需要做的就是将任务 ID 加上结果存到数据库里(Crawlab 现在只支持 MongoDB,后期会开发 MySQL、SQL Server、Postgres 等关系型数据库,有需求的用户可以关注一下)。

在 Scrapy 中,我们需要编写储存逻辑。示意代码如下:

# 引入相关的库,pymongo 是标准连接 MongoDB 的库
import os
from pymongo import MongoClient

# MongoDB 配置参数
MONGO_HOST = '192.168.99.100'
MONGO_PORT = 27017
MONGO_DB = 'crawlab_test'

class JuejinPipeline(object):
    mongo = MongoClient(host=MONGO_HOST, port=MONGO_PORT)  # mongo 连接实例
    db = mongo[MONGO_DB]  # 数据库实例
    col_name = os.environ.get('CRAWLAB_COLLECTION')  # 集合名称,通过环境变量 CRAWLAB_COLLECTION 传过来

    # 如果 CRAWLAB_COLLECTION 不存在,则默认集合名称为 test
    if not col_name:
        col_name = 'test'
        
    col = db[col_name]  # 集合实例

    # 每一个传入 item 会调用的函数,参数分别为 item 和 spider
    def process_item(self, item, spider):
        item['task_id'] = os.environ.get('CRAWLAB_TASK_ID')  # 将 task_id 设置为环境变量传过来的任务 ID
        self.col.save(item)  # 保存 item 在数据库中
        return item

同时,您也需要在items.py中加入task_id字段,已保证值能够被赋上(这很重要)。

上传并配置爬虫

在运行爬虫之前,您需要上传爬虫文件到主节点。步骤如下:

  1. 将爬虫文件打包成 zip(注意,要保证在根目录下直接打包);
  2. 在侧边栏点击“爬虫”导航至爬虫列表,点击“添加爬虫”按钮,选择“自定义爬虫”;
  3. 点击“上传”按钮,选择刚刚打包好的 zip 文件
  4. 上传成功后,爬虫列表中会出现新添加的自定义爬虫,这样就算上传成功了。

可以在爬虫详情中点击“文件”标签,选择一个文件,可以在文件中编辑代码。
爬虫文件
接下来,您需要在“概览”标签中的“执行命令”一栏输入爬虫的 shell 执行命令。Crawlab 的 Docker 镜像里是内置了 scrapy 的,因此可以直接运行 scrapy 爬虫。命令就是scrapy crawl <some_spider>。点击“保存”按钮保存爬虫配置。

运行爬虫任务

然后就是运行爬虫任务了。其实很简单,在“概览”标签中点击“运行”按钮,爬虫任务就开始运行了。如果日志提示找不到 scrapy 命令,可以将scrapy改为绝对路径/usr/local/bin/scrapy,这样就会运行成功。

任务运行情况会在“任务”页面或者爬虫“概览”里展现,会每 5 秒钟更新一次,大家可以在这上面查看。而且在爬虫“结果”标签里,可以预览结果的详情,还可以导出数据成 CSV 文件。

构建持续集成(CI)工作流

对于企业来说,软件开发一般是一个自动化过程。它会经历需求、开发、部署、测试、上线这几个步骤。而这个流程一般是不断迭代(Iterative)的,需要不断更新和发布。

以爬虫为例,您上线了一个爬虫,这个爬虫会定期抓取网站数据。但突然有一天您发现数据抓不到了,您快速定位原因,发现原来是网站改版了,您需要更改爬虫抓取规则来应对网站的改版。总之,您需要发布一个代码更新。最快的做法是直接在线上更改代码。但这样做非常危险:第一,您无法测试您更新后的代码,只能通过不断调整线上代码来测试是否抓取成功;第二,您无法记录这次更改,后期如果出了问题您很可能会忽略掉这次更改,从而导致 bug。您需要做的,无非是将您的爬虫代码用版本管理工具管理起来。我们有很多版本管理工具,最常用的就是 git、subversion,版本管理平台包括 Gitlab、Bitbucket、自搭 Git 仓库等。

当我们更新了代码,我们需要将更新后的代码发布到线上服务器。这时您需要用自己写部署脚本,或者更方便的,用 Jenkins 作为持续集成(Continuous Integration)管理平台。Jenkins 是一个持续集成平台,可以通过获取版本库来更新部署代码,是非常实用的工具,在很多企业中都有用到。下图是如何将 Crawlab 爬虫应用到持续集成工作流程中的例子。

ci

要在 Crawlab 中创建或更新爬虫有两种方式:

  1. 上传打包成后的 zip 文件;
  2. 通过更改主节点中目录CRAWLAB_SPIDER_PATH中的爬虫文件。

我们做持续集成,就是针对第二种方式。步骤如下:

  1. 用 Gitlab 或其他平台搭建好代码仓库;
  2. 在 Jenkins 中创建一个项目,在项目中将代码源指向之前创建的仓库;
  3. 在 Jenkins 项目中编写工作流,将发布地址指向 Crawlab 的CRAWLAB_SPIDER_PATH,如果是 Docker 注意将该地址挂载到宿主机文件系统;
  4. Jenkins 项目的工作可以直接编写,也可以用 Jenkinsfile,具体可以查相关资料;
  5. 这样,每一次代码更新提交到代码仓库后,Jenkins 就会将更新后的代码发布到 Crawlab 里,Crawlab 主节点会将爬虫代码同步到工作节点,以待抓取。

总结

本篇文章主要介绍了爬虫管理平台的定义、如何选择爬虫管理平台,着重介绍了如何搭建开源爬虫管理平台 Crawlab,另外还讲到了如何集成 scrapy 爬虫以及如何打造持续集成工作流。本篇文章没有涉及到的内容还有很多,包括如何 Crawlab 的原理和架构详情、如何使用 Crawlab、如何编写大规模爬虫、如何使用 Jenkins 等等。这些内容可能会在其他文章中发布,请感兴趣的读者多多关注。另外,Crawlab 还有一些需要提升的地方,例如异常监控(零值、空值)、可配置爬虫、可视化抓取、日志集中收集等等。这些功能都将在以后陆续开发和发布,请大家也多多关注。

希望本篇文章对您的工作和学习有所帮助,有任何疑问,请加作者微信 tikazyq1,或者在底部留言提问,作者将尽力回答。谢谢!


有疑问加站长微信联系(非本文作者)

本文来自:Segmentfault

感谢作者:MarvinZhang

查看原文:如何快速搭建实用的爬虫管理平台

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3216 次点击  
加入收藏 微博
下一篇:golang快速排序
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传