最近正在研究一个开源的Python爬虫项目https://github.com/NanmiCoder/MediaCrawler
from abc import ABC, abstractmethod
from typing import Dict, Optional
from playwright.async_api import BrowserContext, BrowserType
class AbstractCrawler(ABC):
@abstractmethod
async def start(self):
pass
@abstractmethod
async def search(self):
pass
@abstractmethod
async def launch_browser(self, chromium: BrowserType, playwright_proxy: Optional[Dict], user_agent: Optional[str],
headless: bool = True) -> BrowserContext:
pass
class AbstractLogin(ABC):
@abstractmethod
async def begin(self):
pass
@abstractmethod
async def login_by_qrcode(self):
pass
@abstractmethod
async def login_by_mobile(self):
pass
@abstractmethod
async def login_by_cookies(self):
pass
class AbstractStore(ABC):
@abstractmethod
async def store_content(self, content_item: Dict):
pass
@abstractmethod
async def store_comment(self, comment_item: Dict):
pass
# TODO support all platform
# only xhs is supported, so @abstractmethod is commented
# @abstractmethod
async def store_creator(self, creator: Dict):
pass
class AbstractStoreImage(ABC):
# TODO: support all platform
# only weibo is supported
# @abstractmethod
async def store_image(self, image_content_item: Dict):
pass
class AbstractApiClient(ABC):
@abstractmethod
async def request(self, method, url, **kwargs):
pass
@abstractmethod
async def update_cookies(self, browser_context: BrowserContext):
pass
定义了一系列的抽象基类,每个类都包含了一些抽象方法。
AbstractCrawler
类定义了与爬虫启动、搜索和启动浏览器相关的抽象方法。
AbstractLogin
类定义了与登录相关的不同方式的抽象方法。
AbstractStore
类定义了与存储内容、评论和创作者信息相关的抽象方法。
AbstractStoreImage
类定义了与存储图像相关的抽象方法。
AbstractApiClient
类定义了与发送请求和更新 cookies
相关的抽象方法。、
这段代码导入了一些模块和类,用于特定的功能:
from abc import ABC, abstractmethod
from typing import Dict, Optional
from playwright.async_api import BrowserContext, BrowserType
- `from abc import ABC, abstractmethod`:从 `abc` 模块导入了 `ABC`(抽象基类)和 `abstractmethod`(用于定义抽象方法),通常用于创建抽象类和规定子类必须实现的方法。
- `from typing import Dict, Optional`:从 `typing` 模块导入了 `Dict`(用于表示字典类型)和 `Optional`(用于表示值可能为特定类型或 `None`),用于类型注解。
- `from playwright.async_api import BrowserContext, BrowserType`:从 `playwright.async_api` 导入了 `BrowserContext` 和 `BrowserType`,这可能与浏览器自动化操作相关。
class AbstractCrawler(ABC):
@abstractmethod
async def start(self):
pass
@abstractmethod
async def search(self):
pass
@abstractmethod
async def launch_browser(self, chromium: BrowserType, playwright_proxy: Optional[Dict], user_agent: Optional[str],
headless: bool = True) -> BrowserContext:
pass
这段 Python 代码定义了一个名为 `AbstractCrawler` 的抽象类。 这个抽象类中有三个抽象方法:
- `start` 方法:是一个异步方法,没有具体实现,子类必须实现它。
- `search` 方法:也是异步的,没有具体实现,需要子类去提供具体的逻辑。
- `launch_browser` 方法:同样是异步的,接收一些参数(`chromium`、`playwright_proxy`、`user_agent` 和 `headless`),并返回 `BrowserContext` 类型的值。子类也需要实现这个方法。 由于这是一个抽象类,它不能被直接实例化,其目的是为了定义一个通用的接口,强制子类按照这个接口去实现这些方法。
- `self`:表示当前类的实例。 - `chromium`:类型为 `BrowserType`,可能是某种表示浏览器类型的对象或类型。
- `playwright_proxy`:类型为 `Optional[Dict]`,表示这个参数可以是一个字典,也可以是 `None`。 - `user_agent`:类型为 `Optional[str]`,可以是字符串或 `None`。
- `headless`:布尔类型,默认值为 `True`。 方法的返回值类型是 `BrowserContext`。 这个方法需要在继承自 `AbstractCrawler` 的子类中被具体实现。
class AbstractLogin(ABC):
@abstractmethod
async def begin(self):
pass
@abstractmethod
async def login_by_qrcode(self):
pass
@abstractmethod
async def login_by_mobile(self):
pass
@abstractmethod
async def login_by_cookies(self):
pass
这个传入的参数是什么?
class AbstractLogin(ABC):
@abstractmethod
async def begin(self):
pass
@abstractmethod
async def login_by_qrcode(self):
pass
@abstractmethod
async def login_by_mobile(self):
pass
@abstractmethod
async def login_by_cookies(self):
pass
定义了一个名为 AbstractLogin
的抽象类,其中包含了四个抽象的异步方法:begin
、login_by_qrcode
、login_by_mobile
和 login_by_cookies
。
你看传入了一个参数是ABC那么我问,ABC是什么?
在这段代码中,ABC
是 Python 标准库中 abc
模块的 AbstractBaseClass
类。
abc
即 abstract base classes
(抽象基类),它提供了一种机制,允许定义抽象方法和属性的基类。通过继承自这样的抽象基类,子类就必须实现基类中定义的抽象方法,否则无法被实例化。
使用 ABC
作为父类可以确保子类遵循特定的接口或规范,强制子类实现某些方法,从而提高代码的健壮性和可维护性。
要使用 ABC
,需要从 abc
模块导入,例如:
from abc import ABC, abstractmethod
然后,可以创建一个继承自 ABC
的类,并使用 @abstractmethod
装饰器来定义抽象方法,如下所示:
class MyAbstractClass(ABC):
@abstractmethod
def my_abstract_method(self):
pass
在上述示例中,MyAbstractClass
就是一个抽象类,其中 my_abstract_method
是一个抽象方法。子类如果要继承 MyAbstractClass
,就必须实现 my_abstract_method
方法。
这样做的好处是可以明确规定子类应该具有哪些方法,使得代码的结构更加清晰,接口更加统一,有助于团队协作和代码的后期维护。同时,在实例化子类对象时,如果子类没有完全实现抽象方法,Python 会在运行时抛出错误,帮助开发者尽早发现问题。
我们接下来往下看
@abstractmethod
async def launch_browser(self, chromium: BrowserType, playwright_proxy: Optional[Dict], user_agent: Optional[str],
headless: bool = True) -> BrowserContext:
pass
定义了一个抽象方法 `launch_browser` 。 以下是对这个方法的分析:
- `@abstractmethod` 装饰器表明这是一个抽象方法,意味着包含这个方法的类如果要被实例化,其子类必须实现这个方法。
- `async` 关键字表明这是一个异步方法。
- `self` 是方法所属对象的引用。
- `chromium` 是 `BrowserType` 类型的参数。
`playwright_proxy` 是一个可选的字典类型参数(`Optional[Dict]`)。
- `user_agent` 是一个可选的字符串类型参数(`Optional[str]`)。
- `headless` 是一个布尔类型参数,默认值为 `True`
- 方法的返回值类型是 `BrowserContext` 。
由于这是一个抽象方法,具体的实现需要在子类中完成。
Optional[str]
通常不是内置的类型,它可能是在代码中自定义的表示方式,或者是基于某些类型注解库(如 typing
模块)的自定义约定。
一般来说,“可选”意味着该参数可以被传递也可以不被传递。如果传递,它的值是字符串类型;如果不传递,可能会有默认值或者在代码逻辑中进行特殊处理。
在 Python 的标准类型注解中,通常使用 typing
模块的 Optional
来表示可选类型。例如,如果 Optional[str]
是基于标准库的表示,它实际上意味着 typing.Optional[str]
,这表示该参数要么是 str
类型的值,要么是 None
。
class AbstractLogin(ABC):
@abstractmethod
async def begin(self):
pass
@abstractmethod
async def login_by_qrcode(self):
pass
@abstractmethod
async def login_by_mobile(self):
pass
@abstractmethod
async def login_by_cookies(self):
pass
这段 Python 代码定义了一个名为 `AbstractLogin` 的抽象类。
在这个类中定义了四个抽象方法:`begin`、`login_by_qrcode`、`login_by_mobile` 和 `login_by_cookies` 。
抽象方法意味着如果有其他类继承自 `AbstractLogin` 类,那么这些子类必须实现这些抽象方法,否则子类也不能被实例化。 `async` 关键字表明这些方法是异步方法。
将这些方法设置为异步方法可能有以下几个原因:
1. 提高性能和响应性:在涉及网络请求、文件读写、数据库操作等可能会阻塞主线程的操作时,使用异步方法可以让程序在等待这些操作完成的同时,去处理其他任务,从而提高整体的性能和响应性。
2. 处理并发任务:当需要同时处理多个登录方式或者在登录过程中需要同时进行多个相关但耗时的操作时,异步方法可以更好地管理并发执行,避免阻塞和等待。
3. 适应特定的库或框架:如果所使用的第三方库或框架提供的登录相关的接口是异步的,那么为了保持一致性和方便集成,将自己的登录方法也定义为异步。
4. 优化资源利用:对于一些资源密集型的登录操作,异步可以更有效地利用系统资源,避免不必要的等待和资源闲置。 总之,使用异步方法可以使登录过程更加高效、灵活和可扩展,以适应复杂的业务需求和现代应用程序对性能和响应性的要求。
class AbstractStore(ABC):
@abstractmethod
async def store_content(self, content_item: Dict):
pass
@abstractmethod
async def store_comment(self, comment_item: Dict):
pass
# TODO support all platform
# only xhs is supported, so @abstractmethod is commented
# @abstractmethod
async def store_creator(self, creator: Dict):
pass
这段代码定义了一个名为 `AbstractStore` 的抽象类。 其中定义了两个必须在子类中实现的异步抽象方法 `store_content` 和 `store_comment` ,用于存储不同类型的内容(分别是 `Dict` 类型的 `content_item` 和 `comment_item` )。 对于 `store_creator` 方法,当前注释掉了 `@abstractmethod` 装饰器,这意味着当前不强制要求子类实现这个方法,但如果未来需要支持所有平台并强制子类实现,就可以取消注释该装饰器。
`content_item: Dict` 这种写法是在 Python 中进行类型注解。
这样写的目的主要有以下几点:
1. 代码的自我说明性:明确指出 `content_item` 变量期望的类型是字典(`Dict`),使其他开发者阅读代码时能更清楚地理解该变量的用途和应包含的数据结构。
2. 静态类型检查(如果使用支持的工具):某些静态类型检查工具(如 `mypy`)可以在开发过程中对代码进行类型检查,提前发现类型不匹配的错误,提高代码的质量和可靠性。
3. 提高代码的可维护性:当代码规模较大或者在团队协作中,清晰的类型注解有助于减少误解和错误,使代码更易于维护和修改。 需要注意的是,Python 本身是动态类型语言,即使不进行类型注解,代码也能正常运行,但类型注解可以提供额外的好处和保障。
还有其他写法吗?
定义字典(Dictionary)的方式除了使用 {key1: value1, key2: value2}
这种直接用花括号括起来、以冒号分隔键值对的形式外,还可以使用 dict()
函数来创建。
content_item = dict(a=1, b=2)
# 或者通过其他可迭代对象(如包含键值对的元组列表)来创建字典
content_item = dict([('a', 1), ('b', 2)])
content_item = dict(a=1, b=2)
使用了关键字参数的方式创建字典,其中 a
和 b
是键,1
和 2
是对应的值。而 content_item = dict([('a', 1), ('b', 2)])
则是通过传入一个包含多个元组的可迭代对象来创建字典,每个元组表示一个键值对。
另外,在进行类型注解时,也可以使用一些与字典相关的类型别名或模块来增强代码的可读性和可维护性。例如,从 Python 3.9 开始,可以使用 typing
模块中的 TypedDict
来创建具有特定键和值类型的字典类型:
from typing import TypedDict
class ContentItem(TypedDict):
key1: str
value1: int
key2: float
value2: bool
然后,可以使用 ContentItem
类型来注解变量:
content_item: ContentItem = {'key1': 'value1', 'value1': 42, 'key2': 3.14, 'value2': True}
这样,通过 TypedDict
定义了一种具有特定键和值类型的字典类型,使得代码更加清晰地表明了字典所期望包含的键以及对应的值的类型。