項(xiàng)目鏈接: https://github.com/jhao104/proxy_pool
爬蟲代理IP池項(xiàng)目,主要功能為定時(shí)采集網(wǎng)上發(fā)布的免費(fèi)代理驗(yàn)證入庫(kù)赖瞒,定時(shí)驗(yàn)證入庫(kù)的代理保證代理的可用性,提供API和CLI兩種使用方式堆巧。同時(shí)你也可以擴(kuò)展代理源以增加代理池IP的質(zhì)量和數(shù)量盐类。
模塊組成:獲取功能俗批、存儲(chǔ)功能痕貌、校驗(yàn)功能溜徙、接口管理
:star:程序主要是啟動(dòng)了
startServer
的API接口服務(wù)湃缎、startScheduler
定時(shí)服務(wù)
startServer->runFlask
:是向外提供了通過(guò)proxyHandler來(lái)獲得Redis中的proxy數(shù)據(jù)startScheduler->sche.add_task(__runProxyFetch)、sche.add_task(__runProxyCheck)
蠢壹,
__runProxyFetch:proxy_fetcher.run()->proxy_queue->Checker("raw", proxy_queue)
獲得各個(gè)代理網(wǎng)站的代理信息后嗓违,進(jìn)行校驗(yàn),校驗(yàn)成功則入庫(kù)__runProxyCheck:proxy in proxy_handler.getAll()->proxy_queue->Checker("use", proxy_queue)
:通過(guò)proxy_handler拿到庫(kù)里所有現(xiàn)存的數(shù)據(jù)后图贸,進(jìn)行有效性校驗(yàn)蹂季,無(wú)效的則刪除,有效的則更新信息作為存儲(chǔ)功能的接口proxyHandler疏日,也是兩個(gè)API服務(wù)與定時(shí)服務(wù)的中介偿洁。程序也是通過(guò)存儲(chǔ)功能,將核心的兩個(gè)功能:<u>定時(shí)抓取的proxy數(shù)據(jù)</u>與<u>提供proxy數(shù)據(jù)給用戶使用</u>成功聯(lián)系在了一起
通過(guò)元類實(shí)現(xiàn)單例模式:ConfigHandler沟优,其可以在任意模塊中以c = ConfigHandler()的形式獲得父能,而不是
ConfigHandler.getInstance()
-
@LazyProperty
懶加載屬性的裝飾器: 只有用到時(shí)才會(huì)加載并將值注入到__dict__
、加載一次后值就不再變化净神、何吝;講解可見:http://www.reibang.com/p/708dc26f9b92——描述符or修飾符實(shí)現(xiàn)class LazyProperty(object): # 在被注解類方法被解釋器運(yùn)行的時(shí)候就會(huì)創(chuàng)建LazyProperty實(shí)例并返回 def __init__(self, func): self.func = func """通過(guò)python描述符來(lái)實(shí)現(xiàn)""" def __get__(self, instance, owner): if instance is None: return self else: # 會(huì)將結(jié)果值通過(guò)setattr方法存入到instance對(duì)象實(shí)例的__dict__中 value = self.func(instance) setattr(instance, self.func.__name__, value) return value class ConfigHandler(withMetaclass(Singleton)): # 返回一個(gè)LazyProperty實(shí)例 @LazyProperty def serverHost(self): return os.environ.get("HOST", setting.HOST) c = ConfigHandler() # 會(huì)觸發(fā)ConfigHandler.__dict__["serverHost"], 然后接而觸發(fā)LazyProperty的__get__,value = self.func(instance)會(huì)得到真正serverHost函數(shù)的值后將其設(shè)置在ConfigHandler instance對(duì)象的__dict__中鹃唯,由于對(duì)象的__dict__["serverHost"]=value優(yōu)先級(jí)高于類的__dict__["serverHost"]=LazyProperty()對(duì)象爱榕,因此之后調(diào)用得到的是value結(jié)果 print(c.serverHost)
__get__
只有訪問(wèn)類屬性的時(shí)候才會(huì)生效,這邊是通過(guò)setattr將serverHost設(shè)置成了ConfigHandler的類屬性 -
封裝了一個(gè)請(qǐng)求工具類
WebRequest
:- 增加了異常處理的功能
- 增加了日志功能
- 請(qǐng)求頭會(huì)得到隨機(jī)UA
- 設(shè)置重試
-
使用click創(chuàng)建子命令:
- 得到一個(gè)click_group
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help']) @click.group(context_settings=CONTEXT_SETTINGS) @click.version_option(version=VERSION) def cli(): """ProxyPool cli工具"""
- 指定group下的子命令
@cli.command(name="server") # 還可以設(shè)置參數(shù): @click.option('--count', default=1, help='Number of greetings.') --> def server(count) def server(): """ 啟動(dòng)api服務(wù) """ click.echo(BANNER) startServer() if __name__ == '__main__': cli()
然后通過(guò)bash腳本同時(shí)開啟兩個(gè)進(jìn)程
#!/usr/bin/env bash python proxyPool.py server & python proxyPool.py schedule
-
DbClient DB工廠類
class DbClient(withMetaclass(Singleton)): def __init__(self, db_conn): self.parseDbConn(db_conn) self.__initDbClient() @classmethod def parseDbConn(cls, db_conn): db_conf = urlparse(db_conn) cls.db_type = db_conf.scheme.upper().strip() cls.db_host = db_conf.hostname cls.db_port = db_conf.port cls.db_user = db_conf.username cls.db_pwd = db_conf.password cls.db_name = db_conf.path[1:] return cls def __initDbClient(self): """ init DB Client :return: """ __type = None if "SSDB" == self.db_type: __type = "ssdbClient" elif "REDIS" == self.db_type: __type = "redisClient" else: pass assert __type, 'type error, Not support DB type: {}'.format(self.db_type) self.client = getattr(__import__(__type), "%sClient" % self.db_type.title())(host=self.db_host, port=self.db_port, username=self.db_user, password=self.db_pwd, db=self.db_name)
-
繼承重寫logging.logger坡慌,可選參數(shù)為
name, level=DEBUG, stream=True, file=True
黔酥,讓每個(gè)功能函數(shù)都能生成單獨(dú)的日志文件,并進(jìn)行了可選控制。相比單例跪者,日志精度更細(xì)棵帽,但也使用起來(lái)也更麻煩,需要考慮什么地方需要渣玲。
-
提供"擴(kuò)展代理"接口
在ProxyFetcher類中添加自定義的獲取代理的靜態(tài)方法逗概, 該方法需要以生成器(yield)形式返回
host:ip
格式的代理-
添加好方法后,修改setting.py文件中的
PROXY_FETCHER
項(xiàng)下添加自定義方法的名字:PROXY_FETCHER = [ "freeProxy01", "freeProxy02", # .... "freeProxyCustom1" # # 確保名字和你添加方法名字一致 ]
schedule
進(jìn)程會(huì)每隔一段時(shí)間抓取一次代理忘衍,下次抓取時(shí)會(huì)自動(dòng)識(shí)別調(diào)用你定義的方法逾苫。
self.log.info("ProxyFetch : start")
# 從配置中拿執(zhí)行函數(shù)
for fetch_source in self.conf.fetchers:
# 判斷ProxyFetcher中是否有定義、是否可調(diào)用
fetcher = getattr(ProxyFetcher, fetch_source, None)
if not fetcher:
self.log.error("ProxyFetch - {func}: class method not exists!".format(func=fetch_source))
continue
if not callable(fetcher):
self.log.error("ProxyFetch - {func}: must be class method".format(func=fetch_source))
continue
thread_list.append(_ThreadFetcher(fetch_source, proxy_dict))
for thread in thread_list:
thread.setDaemon(True)
thread.start()
for thread in thread_list:
thread.join()
self.log.info("ProxyFetch - all complete!")
-
Cpython(默認(rèn)安裝的都是Cpython)中Dict和list枚钓、tuple都是線程安全的
-
以裝飾器的形式將過(guò)濾器將入到容器中
class ProxyValidator(withMetaclass(Singleton)): pre_validator = [] http_validator = [] https_validator = [] @classmethod def addPreValidator(cls, func): cls.pre_validator.append(func) return func # 實(shí)際上執(zhí)行了 formatValidator=ProxyValidator.addPreValidator(formatValidator) # 由于addPreValidator返回了func, 所以formatValidator還是原來(lái)的addPreValidator, 但在類定義的時(shí)候ProxyValidator.pre_validator添加了formatValidator方法 @ProxyValidator.addPreValidator def formatValidator(proxy): """檢查代理格式""" verify_regex = r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5}" _proxy = findall(verify_regex, proxy) return True if len(_proxy) == 1 and _proxy[0] == proxy else False class DoValidator(object): """ 校驗(yàn)執(zhí)行器 """ @classmethod def validator(cls, proxy): """ 校驗(yàn)入口 Args: proxy: Proxy Object Returns: Proxy Object """ http_r = cls.httpValidator(proxy) https_r = False if not http_r else cls.httpsValidator(proxy) proxy.check_count += 1 proxy.last_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S") proxy.last_status = True if http_r else False if http_r: if proxy.fail_count > 0: proxy.fail_count -= 1 proxy.https = True if https_r else False else: proxy.fail_count += 1 return proxy @classmethod def preValidator(cls, proxy): for func in ProxyValidator.pre_validator: if not func(proxy): return False return True
-
-
starter-banner:?jiǎn)?dòng)橫幅
- reflection: No铅搓、adjustment: cewnter、Stretch: Yes搀捷、width: 80
- 還不錯(cuò)的font:
- 5lineoblique——好看
- banner3——清楚
- bell——抽象
- big——清晰
- bigchief——等高線版本星掰、藝術(shù)
- block——塊狀
- bulbhead——可愛
- larry3d——立體3d
- ogre——清晰
- puffy——清晰+一點(diǎn)可愛
- slant——清晰+斜體
-
定時(shí)器框架apschedule配置:
scheduler = BlockingScheduler(logger=scheduler_log, timezone=timezone) scheduler.add_job(__runProxyCheck, 'interval', minutes=2, id="proxy_check", name="proxy檢查") executors = { # job_defaults中的max_instances也受限于max_workers, 所以要大于max_instances;此外max_workers也決定了同時(shí)能處理幾個(gè)同時(shí)發(fā)生的task 'default': {'type': 'threadpool', 'max_workers': 20}, 'processpool': ProcessPoolExecutor(max_workers=5) } job_defaults = { # 合并將所有這些錯(cuò)過(guò)的執(zhí)行合并為一個(gè), 默認(rèn)為True嫩舟。 如果是定時(shí)的存儲(chǔ)任務(wù)的話氢烘,參數(shù)肯定不同,不能合并所以得手動(dòng)設(shè)置False # 像本項(xiàng)目每隔一段時(shí)間抓取到的數(shù)據(jù)也不太一樣至壤,所以無(wú)法直接當(dāng)作一次錯(cuò)誤任務(wù)合并 'coalesce': False, # 默認(rèn)情況下,每個(gè)作業(yè)只允許同時(shí)運(yùn)行一個(gè)實(shí)例枢纠。這意味著像街,如果作業(yè)即將運(yùn)行,但前一次運(yùn)行尚未完成晋渺,則認(rèn)為最近一次運(yùn)行失敗镰绎。通過(guò)在添加作業(yè)時(shí)使用關(guān)鍵字參數(shù),可以設(shè)置調(diào)度程序允許同時(shí)運(yùn)行的特定作業(yè)的最大實(shí)例數(shù)木西。默認(rèn)為1 'max_instances': 10, # 框架會(huì)檢查每個(gè)錯(cuò)過(guò)的執(zhí)行時(shí)間畴栖,如果當(dāng)前還在misfire_grace_time時(shí)間內(nèi),則會(huì)重新嘗試執(zhí)行任務(wù)八千,設(shè)高點(diǎn)就可以避免任務(wù)被漏掉執(zhí)行吗讶。默認(rèn)為1 # "misfire_grace_time": 5 該項(xiàng)目未使用,而是采用了多任務(wù)實(shí)例來(lái)規(guī)避任務(wù)錯(cuò)過(guò)執(zhí)行==>即官方給出兩種方案中的另一種恋捆。任務(wù)錯(cuò)過(guò)信息:Run time of job "say (trigger: interval[0:00:02])" was missed by 0:00:03.010383 } scheduler.configure(executors=executors, job_defaults=job_defaults, timezone=timezone) scheduler.start()
job_defaults參數(shù)含義見官方文檔
注: 經(jīng)過(guò)測(cè)試照皆,在add_task中的func如果起了多個(gè)線程,其執(zhí)行不受限于sche的配置
Python中如果只是使用全局變量則不需要用global聲明(因?yàn)樽兞克褜?huì)由內(nèi)往外)沸停,但是如果需要修改則需要用global聲明膜毁,否則無(wú)法找到相應(yīng)變量
-
生成器:使用了yield關(guān)鍵字的函數(shù)就是生成器,生成器是一類特殊的迭代器。
作用:
- 處理大量數(shù)據(jù):生成器一次返回一個(gè)結(jié)果瘟滨,而不是一次返回所有結(jié)果候醒。比如
sum([i for i in range(10000000000000)])
會(huì)卡機(jī);sum(i for i in range(10000000000000))
則不會(huì) - 代碼更加簡(jiǎn)潔:可以減少變量杂瘸、空間
- 迭代器本身的作用
yield關(guān)鍵字有兩點(diǎn)作用:
保存當(dāng)前運(yùn)行狀態(tài)(斷點(diǎn))倒淫,然后暫停執(zhí)行,即將生成器(函數(shù))掛起胧沫;可以使用next()函數(shù)讓生成器從斷點(diǎn)處繼續(xù)執(zhí)行昌简,即喚醒生成器(函數(shù))
將yield關(guān)鍵字后面表達(dá)式的值作為返回值返回,此時(shí)可以理解為起到了return的作用def __runProxyFetch(): for proxy in proxy_fetcher.run(): proxy_queue.put(proxy) class Fetcher(object): name = "fetcher" def run(self): # ... # 相比使用生成推導(dǎo)式 return [p for p in proxy_dict.values() if DoValidator.preValidator(p.proxy)]绒怨, 使用yield生成器可以節(jié)省空間 for _ in proxy_dict.values(): if DoValidator.preValidator(_.proxy): yield _
- 處理大量數(shù)據(jù):生成器一次返回一個(gè)結(jié)果瘟滨,而不是一次返回所有結(jié)果候醒。比如
-
應(yīng)用部署:
①對(duì)apk換源纯赎;②設(shè)置時(shí)區(qū)
FROM python:3.6-alpine # .. # apk repository RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories # timezone RUN apk add -U tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && apk del tzdata # ... ENTRYPOINT [ "sh", "start.sh" ]
docker-compose.yml
: 鏡像還沒(méi)編譯好的情況。(如果自己改了功能并啟用的話南蹂,需要用這種犬金;或者自己發(fā)布鏡像后用后一種)version: '2' services: proxy_pool: build: . container_name: proxy_pool ports: - "5010:5010" links: - proxy_redis environment: DB_CONN: "redis://@proxy_redis:6379/0" proxy_redis: image: "redis" container_name: proxy_redis
docker-compose.yml
:別人鏡像已經(jīng)編譯好并上傳version: "3" services: redis: image: redis expose: - 6379 web: restart: always image: jhao104/proxy_pool environment: - DB_CONN=redis://redis:6379/0 ports: - "5010:5010" depends_on: - redis
scheduler的邏輯
項(xiàng)目目錄結(jié)構(gòu)默寫:
- settings: 配置文件
- main:?jiǎn)?dòng)文件
- api:提供獲取proxy數(shù)據(jù)接口
- handler:
- loggerHandler:日志類
- configHandler:?jiǎn)卫呐渲媒涌陬?/li>
- ProxyHandler: Proxy CRUD操作類
- fetcher: 代理數(shù)據(jù)獲取類
- db:
- dbClinet: 存儲(chǔ)功能接口類
- redisClient:存儲(chǔ)功能實(shí)現(xiàn)類
- helper
- scheduler: 定時(shí)任務(wù)的定義與啟動(dòng)類
- validator: proxy有效性校驗(yàn)類
- check: 具體執(zhí)行校驗(yàn)邏輯類
- proxy: 獲取的proxy數(shù)據(jù)封裝類
- utils:
- lazyProperty: 懶加載描述器
- singleton: 單例管理器類
- six: python2與python3兼容類
- webRequest: 網(wǎng)絡(luò)請(qǐng)求封裝類