From 4f44414e243de97ea421a3e156b8ecf645a1354d Mon Sep 17 00:00:00 2001 From: shengchenyang <15538221825@163.com> Date: Fri, 17 May 2024 18:08:37 +0800 Subject: [PATCH] docs: update example --- docs/intro/overview.md | 83 +++++++++++++++--------------------------- docs/intro/tutorial.md | 61 ++++++++++++------------------- docs/topics/items.md | 73 +++++++++++++------------------------ 3 files changed, 79 insertions(+), 138 deletions(-) diff --git a/docs/intro/overview.md b/docs/intro/overview.md index 83175c4..d10a718 100644 --- a/docs/intro/overview.md +++ b/docs/intro/overview.md @@ -36,11 +36,9 @@ cd ayuge genspider ``` -下面是从网站 [https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=25&type=](https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=25&type=) 抓取热榜信息的蜘蛛代码: +下面是从 [ayugespidertools](https://ayugespidertools.readthedocs.io/en/latest/) 文档网页中抓取标题信息的蜘蛛代码: ```python -import json - from ayugespidertools.items import DataItem, AyuItem from ayugespidertools.spiders import AyuSpider from scrapy.http import Request @@ -49,8 +47,8 @@ from sqlalchemy import text class DemoOneSpider(AyuSpider): name = "demo_one" - allowed_domains = ["blog.csdn.net"] - start_urls = ["https://blog.csdn.net/"] + allowed_domains = ["readthedocs.io"] + start_urls = ["http://readthedocs.io/"] custom_settings = { # 数据库引擎开关,打开会有对应的 engine 和 engine_conn,可用于数据入库前去重判断 "DATABASE_ENGINE_ENABLED": True, @@ -60,54 +58,36 @@ class DemoOneSpider(AyuSpider): # 开启记录项目相关运行统计信息 "ayugespidertools.pipelines.AyuStatisticsMysqlPipeline": 301, }, - "DOWNLOADER_MIDDLEWARES": { - # 随机请求头 - "ayugespidertools.middlewares.RandomRequestUaMiddleware": 400, - }, } def start_requests(self): - """ - 获取项目热榜的列表数据 - """ yield Request( - url="https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=25&type=", + url="https://ayugespidertools.readthedocs.io/en/latest/", callback=self.parse_first, - headers={ - "referer": "https://blog.csdn.net/rank/list", - }, ) def parse_first(self, response): - _save_table = "demo_one" - data_list = json.loads(response.text)["data"] - for curr_data in data_list: - article_detail_url = curr_data.get("articleDetailUrl") - article_title = curr_data.get("articleTitle") - comment_count = curr_data.get("commentCount") - favor_count = curr_data.get("favorCount") - nick_name = curr_data.get("nickName") + _save_table = "_octree_info" + + li_list = response.xpath('//div[@aria-label="Navigation menu"]/ul/li') + for curr_li in li_list: + octree_text = curr_li.xpath("a/text()").get() + octree_href = curr_li.xpath("a/@href").get() # NOTE: 数据存储方式 1,推荐此风格写法。 - article_item = AyuItem( - article_detail_url=article_detail_url, - article_title=article_title, - comment_count=comment_count, - favor_count=favor_count, - nick_name=nick_name, + octree_item = AyuItem( + octree_text=octree_text, + octree_href=octree_href, _table=_save_table, ) # NOTE: 数据存储方式 2,需要自动添加表字段注释时的写法。但不要风格混用。 """ - article_item = AyuItem( - # 这里也可以写为 article_detail_url = DataItem(article_detail_url),但没有字段 - # 注释功能了,那不如使用 <数据存储方式 1> - article_detail_url=DataItem(article_detail_url, "文章详情链接"), - article_title=DataItem(article_title, "文章标题"), - comment_count=DataItem(comment_count, "文章评论数量"), - favor_count=DataItem(favor_count, "文章赞成数量"), - nick_name=DataItem(nick_name, "文章作者昵称"), + octree_item = AyuItem( + # 这里也可以写为 octree_text = DataItem(octree_text),但没有字段注释 + # 功能了,那不如使用 <数据存储方式 1> + octree_text=DataItem(octree_text, "标题"), + octree_href=DataItem(octree_href, "标题链接"), _table=DataItem(_save_table, "项目列表信息"), ) """ @@ -116,15 +96,12 @@ class DemoOneSpider(AyuSpider): # 但 _table,_mongo_update_rule 等参数就没有 IDE 提示功能了 """ yield { - "article_detail_url": article_detail_url, - "article_title": article_title, - "comment_count": comment_count, - "favor_count": favor_count, - "nick_name": nick_name, + "octree_text": octree_text, + "octree_href": octree_href, "_table": _save_table, } """ - self.slog.info(f"article_item: {article_item}") + self.slog.info(f"octree_item: {octree_item}") # 数据入库逻辑 -> 测试 mysql_engine / mysql_engine_conn 的去重功能。 # 场景对应的 engine 和 engine_conn 也已经给你了,你可自行实现。以下给出示例: @@ -133,37 +110,37 @@ class DemoOneSpider(AyuSpider): if self.mysql_engine_conn: try: _sql = text( - f"""select `id` from `{_save_table}` where `article_detail_url` = "{article_detail_url}" limit 1""" + f"""select `id` from `{_save_table}` where `octree_text` = "{octree_text}" limit 1""" ) result = self.mysql_engine_conn.execute(_sql).fetchone() if not result: self.mysql_engine_conn.rollback() - yield article_item + yield octree_item else: - self.slog.debug(f'标题为 "{article_title}" 的数据已存在') + self.slog.debug(f'标题为 "{octree_text}" 的数据已存在') except Exception: self.mysql_engine_conn.rollback() - yield article_item + yield octree_item else: - yield article_item + yield octree_item # 示例二:使用 pandas 来实现查询如下: """ try: - sql = f'''select `id` from `{_save_table}` where `article_detail_url` = "{article_detail_url}" limit 1''' + sql = f'''select `id` from `{_save_table}` where `octree_text` = "{octree_text}" limit 1''' df = pandas.read_sql(sql, self.mysql_engine) # 如果为空,说明此数据不存在于数据库,则新增 if df.empty: - yield article_item + yield octree_item # 如果已存在,1). 若需要更新,请自定义更新数据结构和更新逻辑;2). 若不用更新,则跳过即可。 else: - self.slog.debug(f"标题为 ”{article_title}“ 的数据已存在") + self.slog.debug(f"标题为 ”{octree_text}“ 的数据已存在") except Exception as e: if any(["1146" in str(e), "1054" in str(e), "doesn't exist" in str(e)]): - yield article_item + yield octree_item else: self.slog.error(f"请查看数据库链接或网络是否通畅!Error: {e}") """ diff --git a/docs/intro/tutorial.md b/docs/intro/tutorial.md index 4e65ddb..07e52bb 100644 --- a/docs/intro/tutorial.md +++ b/docs/intro/tutorial.md @@ -2,7 +2,7 @@ 在本教程中,我们假设您的系统上已经安装了 `ayugespidertools`。 -> 我们要抓取 [blog.csdn.net](https://blog.csdn.net/),这是一个知识问答社区的网站。 +> 我们要抓取 [ayugespidertools readthedocs](https://ayugespidertools.readthedocs.io/en/latest/) 的网页内容,这是本库的文档网站。 > 本教程将引导您完成这些任务: @@ -49,20 +49,18 @@ DemoSpider/ ## 我们的第一个 Spider -这是我们第一个 `Spider` 的代码。`demo_one.py` 将其保存在项目目录下命名的文件 `DemoSpider/spiders`中: +这是我们第一个 `Spider` 的代码 `demo_one.py` ,将其保存在项目目录下命名的文件 `DemoSpider/spiders`中: ```python -import json - from ayugespidertools.items import AyuItem from ayugespidertools.spiders import AyuSpider from scrapy.http import Request -class DemoEightSpider(AyuSpider): - name = "demo_eight" - allowed_domains = ["blog.csdn.net"] - start_urls = ["https://blog.csdn.net/"] +class DemoOneSpider(AyuSpider): + name = "demo_one" + allowed_domains = ["readthedocs.io"] + start_urls = ["http://readthedocs.io/"] custom_settings = { # 打开 mysql 引擎开关,用于数据入库前更新逻辑判断 "DATABASE_ENGINE_ENABLED": True, @@ -72,53 +70,40 @@ class DemoEightSpider(AyuSpider): # 激活此项则数据会存储至 MongoDB "ayugespidertools.pipelines.AyuFtyMongoPipeline": 301, }, - "DOWNLOADER_MIDDLEWARES": { - # 随机请求头 - "ayugespidertools.middlewares.RandomRequestUaMiddleware": 400, - }, } def start_requests(self): - """ - get 请求首页,获取项目列表数据 - """ yield Request( - url="https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=25&type=", + url="https://ayugespidertools.readthedocs.io/en/latest/", callback=self.parse_first, - headers={ - "referer": "https://blog.csdn.net/rank/list", - }, ) def parse_first(self, response): - data_list = json.loads(response.text)["data"] - for curr_data in data_list: - article_detail_url = curr_data.get("articleDetailUrl") - article_title = curr_data.get("articleTitle") - comment_count = curr_data.get("commentCount") - favor_count = curr_data.get("favorCount") - nick_name = curr_data.get("nickName") - - article_item = AyuItem( - article_detail_url=article_detail_url, - article_title=article_title, - comment_count=comment_count, - favor_count=favor_count, - nick_name=nick_name, - _table="_article_info_list", + _save_table = "_octree_info" + + li_list = response.xpath('//div[@aria-label="Navigation menu"]/ul/li') + for curr_li in li_list: + octree_text = curr_li.xpath("a/text()").get() + octree_href = curr_li.xpath("a/@href").get() + + octree_item = AyuItem( + octree_text=octree_text, + octree_href=octree_href, + _table=_save_table, ) - yield article_item + self.slog.info(f"octree_item: {octree_item}") + yield octree_item ``` 如您所见,`Spider` 子类化 `AyuSpider` 并定义了一些属性和方法: -- `name`: 标识蜘蛛。在一个项目中必须是唯一的,即不能为不同的Spiders设置相同的名字。 +- `name`: 标识蜘蛛。在一个项目中必须是唯一的,即不能为不同的 `Spiders` 设置相同的名字。 - `start_requests()`: 必须返回一个可迭代的请求(你可以返回一个请求列表或编写一个生成器函数),蜘蛛将从中开始爬行。后续请求将从这些初始请求中依次生成。 -- `parse_first()`:将被调用以处理为每个请求下载的响应的方法。`response` 参数是 `TextResponse` 的一个实例,它保存页面内容,并有进一步的有用方法来处理它。该 `parse_first()` 方法通常解析响应,将抓取的数据提取为字典,并找到要遵循的新 URL 并从中创建新请求 ( `Request`)。 +- `parse_first()`:将被调用以处理为每个请求下载的响应的方法。`response` 参数是 `TextResponse` 的一个实例,它保存页面内容,并有进一步的有用方法来处理它。该 `parse_first()` 方法通常解析响应,将抓取的数据提取为字典,并找到要遵循的新 `URL` 并从中创建新请求 ( `Request`)。 另外,一些其它注意事项: -- 示例中的一些配置和一些功能并不是每个项目中都必须要编写和配置的,只是用于展示一些功能 +- 示例中的一些配置和一些功能并不是每个项目中都必须要编写和配置的,只是用于展示一些功能; - 据上条可知,可以写出很简洁的代码,删除你认为的无关配置和方法并将其配置成你自己的模板就更容易适配更多人的使用场景。 diff --git a/docs/topics/items.md b/docs/topics/items.md index c33672b..70e2315 100644 --- a/docs/topics/items.md +++ b/docs/topics/items.md @@ -198,8 +198,6 @@ item = AyuItem(_table="ta") `AyuItem` 在 `spider` 中常用的基础使用方法示例,以本库模板中的 `basic.tmpl` 为例来作解释: ```python -import json - from ayugespidertools.items import AyuItem from ayugespidertools.spiders import AyuSpider from scrapy.http import Request @@ -208,8 +206,8 @@ from sqlalchemy import text class DemoOneSpider(AyuSpider): name = "demo_one" - allowed_domains = ["csdn.net"] - start_urls = ["https://www.csdn.net/"] + allowed_domains = ["readthedocs.io"] + start_urls = ["http://readthedocs.io/"] custom_settings = { # 数据库引擎开关,打开会有对应的 engine 和 engine_conn,可用于数据入库前去重判断 "DATABASE_ENGINE_ENABLED": True, @@ -219,75 +217,56 @@ class DemoOneSpider(AyuSpider): # 激活此项则数据会存储至 MongoDB "ayugespidertools.pipelines.AyuFtyMongoPipeline": 301, }, - "DOWNLOADER_MIDDLEWARES": { - # 随机请求头 - "ayugespidertools.middlewares.RandomRequestUaMiddleware": 400, - }, } def start_requests(self): - """ - get 请求首页,获取项目列表数据 - """ yield Request( - url="https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=25&type=", + url="https://ayugespidertools.readthedocs.io/en/latest/", callback=self.parse_first, - headers={ - "referer": "https://blog.csdn.net/rank/list", - }, - cb_kwargs={ - "curr_site": "csdn", - }, ) - def parse_first(self, response, curr_site): - # 日志使用 scrapy 的 self.logger 或本库的 self.slog - self.slog.info(f"当前采集的站点为: {curr_site}") + def parse_first(self, response): + _save_table = "_octree_info" - _save_table = "_article_info_list" # 你可以自定义解析规则,使用 lxml 还是 response.css response.xpath 等等都可以。 - data_list = json.loads(response.text)["data"] - for curr_data in data_list: - article_detail_url = curr_data.get("articleDetailUrl") - article_title = curr_data.get("articleTitle") - comment_count = curr_data.get("commentCount") - favor_count = curr_data.get("favorCount") - nick_name = curr_data.get("nickName") - - article_item = AyuItem( - article_detail_url=article_detail_url, - article_title=article_title, - comment_count=comment_count, - favor_count=favor_count, - nick_name=nick_name, + li_list = response.xpath('//div[@aria-label="Navigation menu"]/ul/li') + for curr_li in li_list: + octree_text = curr_li.xpath("a/text()").get() + octree_href = curr_li.xpath("a/@href").get() + + octree_item = AyuItem( + octree_text=octree_text, + octree_href=octree_href, _table=_save_table, - # 这里表示 MongoDB 存储场景以 article_detail_url 为去重规则,若存在则更新,不存在则新增 - _mongo_update_rule={"article_detail_url": article_detail_url}, + # 可选参数:这里表示 MongoDB 存储场景以 octree_text 为去重规则,若存在则更新,不存在则新增 + _mongo_update_rule={"octree_text": octree_text}, ) - self.slog.info(f"article_item: {article_item}") + # 日志使用 scrapy 的 self.logger 或本库的 self.slog + self.slog.info(f"octree_item: {octree_item}") # 注意:同时存储至 mysql 和 mongodb 时,不建议使用以下去重方法,会互相影响。 # 此时更适合: - # 1.mysql 添加唯一索引去重(本库会根据 on duplicate key update 更新), + # 1.mysql 添加唯一索引去重(结合 odku_enable 配置,本库会根据 on duplicate key update 更新), # mongoDB 场景下设置 _mongo_update_rule 参数即可; - # 2.或者添加爬取时间字段并每次新增的场景,即不去重,请根据使用场景自行选择。 - # 这里只是为了介绍使用 mysql_engine / mysql_engine_conn 来对 mysql 去重的方法。 + # 2.或者添加爬取时间字段并每次新增的场景,即不去重,请根据使用场景自行选择; + # 3.同时存储多个数据库场景更推荐使用第三方去重来统一管理,比如 scrapy-redis,布隆过滤等。 + # 这里只是为了介绍使用 mysql_engine_conn 来对 mysql 去重的方法。 if self.mysql_engine_conn: try: _sql = text( - f"""select `id` from `{_save_table}` where `article_detail_url` = "{article_detail_url}" limit 1""" + f"""select `id` from `{_save_table}` where `octree_text` = "{octree_text}" limit 1""" ) result = self.mysql_engine_conn.execute(_sql).fetchone() if not result: self.mysql_engine_conn.rollback() - yield article_item + yield octree_item else: - self.slog.debug(f'标题为 "{article_title}" 的数据已存在') + self.slog.debug(f'标题为 "{octree_text}" 的数据已存在') except Exception as e: self.mysql_engine_conn.rollback() - yield article_item + yield octree_item else: - yield article_item + yield octree_item ``` > 由上可知,本库中的 `Item` 使用方法还是很方便的。