diff --git a/LICENSE b/LICENSE index 261eeb9..d1ef075 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright [2021] [smart-spider,liangbaikai] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/Pipfile b/Pipfile index d815e4b..ab405cb 100644 --- a/Pipfile +++ b/Pipfile @@ -4,30 +4,22 @@ url = "https://mirrors.163.com/pypi/simple/" verify_ssl = true [dev-packages] -#mypy = "*" -#fastapi = "*" -#uvicorn = "*" -#jinja2 = "*" pytest = "*" - +mkdocs = "*" +pymysql = "*" +aiomysql = "*" +pyppeteer = "*" +ruia = "*" +ruia-ua = "*" +requests = "*" +fastapi = "*" [packages] aiohttp = "*" lxml = "*" -#bitarray = "*" -requests = "*" -fastapi = "*" uvicorn = {extras = ["standard"],version = "*"} python-multipart = "*" -ruia = "*" -ruia-ua = "*" jsonpath = "*" parsel = "*" -pytest = "*" -pyppeteer = "*" -pymysql = "*" -aiomysql = "*" -mkdocs = "*" cchardet = "*" - [requires] python_version = "3.7" diff --git a/launcher.py b/launcher.py index db134b3..3442bb3 100644 --- a/launcher.py +++ b/launcher.py @@ -9,6 +9,7 @@ from smart.runer import CrawStater from spiders.db.sanicdb import SanicDB from spiders.govs import GovsSpider, ArticelItem +from spiders.image_spider import ImageSpider from spiders.ipspider2 import IpSpider3, GovSpider, IpSpider, ApiSpider from spiders.js.js_spider import JsSpider, Broswer from spiders.json_spider import JsonSpider @@ -23,7 +24,7 @@ async def do_pip(spider_ins, item): @piplinestest.pipline(2) -def do_pip2(spider_ins, item): +def pip2(spider_ins, item): print(f"我是item2 {item.results}") return item @@ -62,4 +63,4 @@ def start1(): spider1 = GovsSpider() spider2 = JsonSpider() js_spider = JsSpider() - starter.run_many([IpSpider()], middlewire=middleware2, pipline=piplinestest) + starter.run_many([spider1], middlewire=middleware2, pipline=piplinestest) diff --git a/smart/core.py b/smart/core.py index 1cf5b73..21a0b79 100644 --- a/smart/core.py +++ b/smart/core.py @@ -8,12 +8,9 @@ import asyncio import importlib import inspect -import time -import traceback import uuid from asyncio import Lock from collections import deque -from contextlib import suppress from typing import Dict from smart.log import log @@ -101,9 +98,7 @@ def _check_complete_callback(self, task): async def start(self): self.spider.on_start() - # self.spider self.request_generator_queue.append((self.spider, iter(self.spider))) - # self.request_generator_queue.append( iter(self.spider)) # core implenment while not self.stop: # paused @@ -120,7 +115,6 @@ async def start(self): request = self.scheduler.get() can_stop = self._check_can_stop(request) - # if request is None and not self.task_dict: if can_stop: # there is no request and the task has been completed.so ended self.log.debug( @@ -134,7 +128,7 @@ async def start(self): if resp is None: # let the_downloader can be scheduled, test 0.001-0.0006 is better - await asyncio.sleep(0.0005) + await asyncio.sleep(0.005) continue custome_callback = resp.request.callback @@ -148,8 +142,9 @@ async def start(self): self.spider.state = "closed" self.spider.on_close() - self.log.debug(f" engine stoped..") + # wait some resource to freed await asyncio.sleep(0.15) + self.log.debug(f" engine stoped..") def pause(self): self.log.info(f" out called pause.. so engine will pause.. ") diff --git a/smart/downloader.py b/smart/downloader.py index 4be863f..ce16b70 100644 --- a/smart/downloader.py +++ b/smart/downloader.py @@ -29,31 +29,20 @@ def fetch(self, request: Request) -> Response: pass -# class RequestsDown(BaseDown): -# def fetch(self, request: Request) -> Response: -# import requests -# res = requests.get(request.url, -# timeout=request.timeout or 3, -# ) -# response = Response(body=res.content, request=request, -# headers=res.headers, -# cookies=res.cookies, -# status=res.status_code) -# return response - - class AioHttpDown(BaseDown): async def fetch(self, request: Request) -> Response: - async with aiohttp.ClientSession() as clicnt: - resp = await clicnt.request(request.method, - request.url, - timeout=request.timeout or 10, - headers=request.header or {}, - cookies=request.cookies or {}, - data=request.data or {}, - **request.extras or {} - ) + session = None + try: + session = request.session or aiohttp.ClientSession() + resp = await session.request(request.method, + request.url, + timeout=request.timeout or 10, + headers=request.header or {}, + cookies=request.cookies or {}, + data=request.data or {}, + **request.extras or {} + ) byte_content = await resp.read() headers = {} if resp.headers: @@ -63,6 +52,9 @@ async def fetch(self, request: Request) -> Response: headers=headers, cookies=resp.cookies ) + finally: + if request.session is None and session: + await session.close() return response @@ -78,6 +70,7 @@ def __init__(self, scheduler: Scheduler, middwire: Middleware = None, seq=100, d # the real to fetch resource from internet self.downer = downer self.log.info(f" downer loaded {self.downer.__class__.__name__}") + async def download(self, request: Request): spider = request.__spider__ max_retry = spider.cutome_setting_dict.get("req_max_retry") or gloable_setting_dict.get( diff --git a/smart/field.py b/smart/field.py index 14e77a7..78b0fb3 100644 --- a/smart/field.py +++ b/smart/field.py @@ -7,7 +7,6 @@ # ------------------------------------------------------------------ import json import re -from abc import abstractmethod, ABC from typing import Union, Iterable, Callable, Any import jsonpath @@ -240,2091 +239,4 @@ def extract(self, html: Any): if __name__ == '__main__': - html = """ - - - - - - - -武动乾坤小说_天蚕土豆_武动乾坤最新章节_武动乾坤无弹窗_新笔趣阁 - - - - - - - - - - - - - - - - - - - -
- -
- - -
- - - -
-
-
- 新笔趣阁 > 玄幻小说 > 武动乾坤最新章节目录 -
-
-
-

武动乾坤

-

作    者:天蚕土豆

-

动    作:加入书架, 投推荐票, 直达底部

-

最后更新:2017-11-09 06:33:19

-

最新章节:新书大主宰已发。

-
-
-

手机阅读《武动乾坤》无弹窗纯文字全文免费阅读 - -

- -

修炼一途,乃窃阴阳,夺造化,转涅盘,握生死,掌轮回。 - 武之极,破苍穹,动乾坤! - 新书求收藏,求推荐,谢大家o(n_n)o~ -

-
-
- - -
- -
- -
-
-
- - -
第一章 林动
-
第二章 通背拳
-
第三章 古怪的石池
-
第四章 石池之秘
- - -
第五章 神秘石符
-
第六章 七响
-
第七章 淬体第四重
-
第八章 冲突
- - -
第九章 林宏
-
第十章 金玉枝
-
第十一章 阴珠
-
第十二章 第十响
- - -
第十三章 疗伤
-
第十四章 五等阴煞之气
-
第十五章 淬体第五重
-
第十六章 八荒掌
- - -
第十七章 蝎虎
-
第十八章 元力种子
-
第十九章 族比前的突破
-
第二十章 族比开始
- - -
第二十一章 林陨
-
第二十二章 艺惊全场
-
第二十三章 前三
-
第二十四章 完胜
- - -
第二十五章 接管事务
-
第二十六章 狩猎
-
第二十七章 武学馆
-
第二十八章 奇门印,残篇
- - -
第二十九章 石符变故
-
第三十章 小成
-
第三十一章 妖孽
-
第三十二章 地下交易所
- - -
第三十三章 谢婷
-
第三十四章 雷力
-
第三十五章 初步交手
-
第三十六章 聚餐
- - -
第三十七章 突破
-
第三十八章 变故
-
第三十九章 地元境!
-
第四十章 狩猎开始
- - -
第四十一章 罗城
-
第四十二章 火蟒虎
-
第四十三章 抢崽
-
第四十四章 得手
- - -
第四十五章 剑拔弩张
-
第四十六章 震惊全场
-
第四十七章 激战
-
第四十八章 收获
- - -
第四十九章 武学奇才
-
第五十章 青元功
-
第五十一章 小炎
-
第五十二章 家族之事
- - -
第五十三章 铁木庄
-
第五十四章 毁土
-
第五十五章 搏杀
-
第五十六章 泥土中的阳罡之气
- - -
第五十七章 阳元石
-
第五十八章 矿脉
-
第五十九章 杀豹
-
第六十章 磨练
- - -
第六十一章 阳元丹
-
第六十二章 炎城
-
第六十三章 符师
-
第六十四章 岩大师
- - -
第六十五章 绊子
-
第六十六章 神动篇
-
第六十七章 阴云
-
第六十八章 黑龙寨
- - -
第六十九章 大难
-
第七十章 震撼
-
第七十一章 突破!
-
第七十二章 退敌
- - -
第七十三章 暴怒的林震天
-
第七十四章 血洗黑龙寨
-
第七十五章 碎元梭
-
第七十六章 神秘兽骸
- - -
第七十七章 妖异花朵
-
第七十八章 暴涨的精神力
-
第七十九章 地下坊会
-
第八十章 遇袭
- - -
第八十一章 反杀
-
第八十二章 一死一伤
-
第八十三章 古木
-
第八十四章 古漩符印
- - -
第八十五章 一印符师
-
第八十六章 救援
-
第八十七章 断后
-
第八十八章 突破
- - -
第八十九章 试探
-
第九十章 小元丹境
-
第九十一章 联姻
-
第九十二章 雷谢两家的打算
- - -
第九十三章 古大师
-
第九十四章 暴露
-
第九十五章 符师对决
-
第九十六章 本命灵符
- - -
第九十七章 杀!
-
第九十八章 隐患
-
第九十九章 石符内的“鼠”
-
第一百章 天妖貂
- - -
第一百零一章 血衣临门
-
第一百零二章 赌约
-
第一百零三章 暂离
-
第一百零四章 万金拍卖场
- - -
第一百零五章 销金窟
-
第一百零六章 萱素
-
第一百零七章 丹仙池
-
第一百零八章 尖螺波
- - -
第一百零九章 宋青
-
第一百一十章 动身
-
第一百一十一章 仙池之争
-
第一百一十二章 最后一战
- - -
第一百一十三章 化血归元功
-
第一百一十四章 进入丹仙池
-
第一百一十五章 化气精旋
-
第一百一十六章 碧水妖蟒
- - -
第一百一十七章 两兽相斗
-
第一百一十八章 供奉与花销
-
第一百一十九章 赚钱
-
第一百二十章 三阳决
- - -
第一百二十一章 苦修
-
第一百二十二章 福不单行
-
第一百二十三章 小元丹,二印符师
-
第一百二十四章 展现实力
- - -
第一百二十五章 生死斗
-
第一百二十六章 对战魏通
-
第一百二十七章 激战
-
第一百二十八章 杀!
- - -
第一百二十九章 落幕
-
第一百三十章 塔斗
-
第一百三十一章 紫月
-
第一百三十二章 再说一次
- - -
第一百三十三章 曹铸
-
第一百三十四章 冰玄剑
-
第一百三十五章 塔斗开始
-
第一百三十六章 第五层
- - -
第一百三十七章 打劫
-
第一百三十八章 追赶
-
第一百三十九章 进入第七层
-
第一百四十章 意志
- - -
第一百四十一章 化生符阵
-
第一百四十二章 胜负
-
第一百四十三章 三印符师
-
第一百四十四章 祖符
- - -
第一百四十五章 精神地
-
第一百四十六章 一波再起
-
第一百四十七章 鸟东西
-
第一百四十八章 指教
- - -
第一百四十九章 对战鬼阎
-
第一百五十章 化生符阵显威
-
第一百五十一章 四大势力
-
第一百五十二章 震慑
- - -
第一百五十三章 煞魔之体
-
第一百五十四章 妖血朱果
-
第一百五十五章 古墓府
-
第一百五十六章 内族之人
- - -
第一百五十七章 林尘
-
第一百五十八章 完美的操控
-
第一百五十九章 小圆满
-
第一百六十章 天炎山脉
- - -
第一百六十一章 宋刀
-
第一百六十二章 灵宝
-
第一百六十三章 化生符阵第三重
-
第一百六十四章 强夺
- - -
第一百六十五章 夜色下的落幕
-
第一百六十六章 林琅天
-
第一百六十七章 四大年轻顶尖强者!
-
第一百六十八章 破封
- - -
第一百六十九章 暴富
-
第一百七十章 洗劫妖灵室
-
第一百七十一章 六件灵宝
-
第一百七十二章 抢宝
- - -
第一百七十三章 天鳞古戟
-
第一百七十四章 符傀
-
第一百七十五章 中等符傀
-
第一百七十六章 火海
- - -
第一百七十七章 涅盘心
-
第一百七十八章 强夺阳气
-
第一百七十九章 墓府主人
-
第一百八十章 麻烦
- - -
第一百八十一章 今日事,百倍还
-
第一百八十二章 符祖
-
第一百八十三章 激斗王炎!
-
第一百八十四章 符傀之威
- - -
第一百八十五章 救援
-
第一百八十六章 山顶之谈
-
第一百八十七章 收获
-
第一百八十八章 血拼
- - -
第一百八十九章 以一敌三
-
第一百九十章 戟法之威
-
第一百九十一章 解决
-
第一百九十二章 血狼帮之殇
- - -
第一百九十三章 引爆阴煞之气
-
第一百九十四章 黑色阴丹
-
第一百九十五章 开启石符
-
第一百九十六章 大日雷体
- - -
第一百九十七章 离别前的挑战
-
第一百九十八章 战城主
-
第一百九十九章 化蛟戟
-
第两百章 森林修行
- - -
第两百零一章 引雷淬体
-
第两百零二章 吞噬雷霆
-
第两百零三章 小炎之危
-
第两百零四章 大阳郡狄家
- - -
第两百零五章 狄腾
-
第两百零六章 雷源晶兽
-
第两百零七章 抢夺雷源
-
第两百零八章 大战造形境
- - -
第两百零九章 炼化雷源
-
第两百一十章 山洞闭关
-
第两百一十一章 实力大涨
-
第两百一十二章 显威
- - -
第两百一十三章 击溃
-
第两百一十四章 敲诈
-
第两百一十五章 迷雾森林
-
第两百一十六章 鹰之武馆
- - -
第两百一十七章 迷雾豹鳄王
-
第两百一十八章 露底
-
第两百一十九章 大荒古碑
-
第两百二十章 血鹫武馆
- - -
第两百二十一章 狠揍
-
第两百二十二章 美人献身
-
第两百二十三章 罗鹫
-
第两百二十四章 武斗台
- - -
第两百二十五章 战造形境大成
-
第两百二十六章 魔猿变
-
第两百二十七章 击溃
-
第两百二十八章 邂逅
- - -
第两百二十九章 魔猿精血
-
第两百三十章 远古龙猿
-
第两百三十一章 远古废涧
-
第两百三十二章 古剑门
- - -
第两百三十三章 万兽果
-
第两百三十四章 驱虎吞狼
-
第两百三十五章 古剑憾龙猿
-
第两百三十六章 惊天大战
- - -
第两百三十七章 精血到手
-
第两百三十八章 炼化龙猿精血
-
第两百三十九章 炼化成功
-
第两百四十章 肉搏
- - -
第两百四十一章 大傀城
-
第两百四十二章 慕芊芊
-
第两百四十三章 拍卖会
-
第两百四十四章 蕴神蒲团
- - -
第两百四十五章 程大师
-
第两百四十六章 进化的天鳞古戟
-
第两百四十七章 节外生枝
-
第两百四十八章 蒲团之谜
- - -
第四百四十九章 元精之力
-
第两百五十章 围剿
-
第两百五十一章 灵符师
-
第两百五十二章 激斗华宗
- - -
第两百五十三章 破甲
-
第两百五十四章 轰杀
-
第两百五十五章 大丰收
-
第两百五十六章 全力突破
- - -
第两百五十七章 争分夺秒
-
第两百五十八章 追寻而至
-
第两百五十九章 硬憾造气大成
-
第两百六十章 震退
- - -
第两百六十一章 黑衣青年
-
第两百六十二章 大荒古原
-
第两百六十三章 腾儡
-
第两百六十四章 再遇
- - -
第两百六十五章 再战王炎
-
第两百六十六章 完虐
-
第两百六十七章 犀利言辞
-
第两百六十八章 封印消失
- - -
第两百六十九章 古碑空间
-
第两百七十章 阴风炼体
-
第两百七十一章 石亭骸骨
-
第两百七十二章 冤家路窄
- - -
第两百七十三章 断臂
-
第两百七十四章 核心地带
-
第两百七十五章 符傀巢穴
-
第两百七十六章 抢夺
- - -
第两百七十七章 收服高等符傀
-
第两百七十八章 黑色祭坛
-
第两百七十九章 黑瞳老人
-
第两百八十章 封锁
- - -
第两百八十一章 阴魔杀
-
第两百八十二章 高等符傀之力
-
第两百八十三章 造化武碑
-
第两百八十四章 十道蒲团
- - -
第两百八十五章 抢夺席位
-
第两百八十六章 显凶威
-
第两百八十七章 强势擒获
-
第两百八十八章 占据
- - -
第两百八十九章 大荒囚天指
-
第两百九十章 传承武学
-
第两百九十一章 造气境
-
第两百九十二章 宗派宝藏
- - -
第两百九十三章 远古血蝠龙
-
第两百九十四章 斩杀血蝠龙
-
第两百九十五章 飞来横财
-
第两百九十六章 逃
- - -
第两百九十七章 夺宝再逃
-
第两百九十八章 黑色符文
-
第两百九十九章 诅咒之力
-
第三百章 实力提升
- - -
第三百零一章 上门挑衅
-
第三百零二章 强势出手
-
第三百零三章 血屠手曹震
-
第三百零四章 对战半步造化
- - -
第三百零五章 九步震天踏
-
第三百零六章 安然而退
-
第三百零七章 情报
-
第三百零八章 紫影九破
- - -
第三百零九章 阴傀城
-
第三百一十章 腾刹
-
第三百一十一章 黑瞳虚影
-
第三百一十二章 破阵
- - -
第三百一十三章 造化境大成
-
第三百一十四章 抢了就跑
-
第三百一十五章 大乱
-
第三百一十六章 心狠手辣
- - -
第三百一十七章 玄阴涧
-
第三百一十八章 无路可逃
-
第三百一十九章 绝境
-
第三百二十章 封印破解
- - -
第三百二十一章 黑暗之界
-
第三百二十二章 祖符认可
-
第三百二十三章 高级灵符师
-
第三百二十四章 实力暴涨
- - -
第三百二十五章 深入玄阴涧
-
第三百二十六章 危难
-
第三百二十七章 晋入半步造化
-
第三百二十八章 滔天杀意
- - -
第三百二十九章 复仇
-
第三百三十章 大战造化大成
-
第三百三十一章 血战
-
第三百三十二章 煞气逼人
- - -
第三百三十三章 祖符之威
-
第三百三十四章 灭宗
-
第三百三十五章 斩草除根
-
第三百三十六章 血灵傀
- - -
第三百三十七章 下狠心
-
第三百三十八章 封印血灵傀
-
第三百三十九章 晋级的需求
-
第三百四十章 离开
- - -
第三百四十一章 凑齐妖血
-
第三百四十二章 雷体大成
-
第三百四十三章 麻衣老人
-
第三百四十四章 险象环生
- - -
第三百四十五章 大炎郡
-
第三百四十六章 族会!
-
第三百四十七章 强大的青檀
-
第三百四十八章 林动归来!
- - -
第三百四十九章 滚下来
-
第三百五十章 何谓嚣张
-
第三百五十一章 一拳轰爆
-
第三百五十二章 给你一个字
- - -
第三百五十三章 对战林琅天!
-
第三百五十四章 龙争虎斗
-
第三百五十五章 大天凰印!
-
第三百五十六章 底牌层出
- - -
第三百五十七章 灵轮镜
-
第三百五十八章 拼命相搏
-
第三百五十九章 林梵
-
第三百六十章 落幕
- - -
第三百六十一章 种子选拔
-
第三百六十二章 大炎王朝外的世界
-
第三百六十三章 族藏
-
第三百六十四章 暗袭
- - -
第三百六十五章 神秘的黑色小山
-
第三百六十六章 重狱峰
-
第三百六十七章 造化境小成
-
第三百六十八章 给脸不要脸
- - -
第三百六十九章 不留情面
-
第三百七十章 再次相对
-
第三百七十一章 残酷
-
第三百七十二章 赶往皇城
- - -
第三百七十三章 天才云集
-
第三百七十四章 青衫莫凌
-
第三百七十五章 选拔开始
-
第三百七十六章 搬山二将
- - -
第三百七十七章 皇普影
-
第三百七十八章 暗袭之术
-
第三百七十九章 破影
-
第三百八十章 最后的对手
- - -
第三百八十一章 王钟
-
第三百八十二章 血魔修罗枪
-
第三百八十三章 苦战
-
第三百八十四章 血战
- - -
第三百八十五章 名额
-
第三百八十六章 选拔落幕
-
第三百八十七章 暴怒的王雷
-
第三百八十八章 夜谈
- - -
第三百八十九章 圣灵潭
-
第三百九十章 各施手段
-
第三百九十一章 抢夺能量
-
第三百九十二章 抢光
- - -
第三百九十三章 林琅天体内的神秘存在
-
第三百九十四章 收获不小
-
第三百九十五章 骨枪
-
第三百九十六章 炼化天鳄骨枪
- - -
第三百九十七章 血灵傀之变
-
第三百九十八章 进入远古战场!
-
第三百九十九章 陌生的空间
-
第四百章 聚集点
- - -
第四百零一章 圣光王朝
-
第四百零二章 妖潮
-
第四百零三章 冲突
-
第四百零四章 屠戮
- - -
第四百零五章 虎口夺食
-
第四百零六章 黎盛
-
第四百零七章 战造化境巅峰
-
第四百零八章 圣象崩天撞
- - -
第四百零九章 五指动乾坤
-
第四百一十章 尽数轰杀
-
第四百一十一章 逼走
-
第四百一十二章 清点收获
- - -
第四百一十三章 圣光王朝大师兄
-
第四百一十四章 情报
-
第四百一十五章 小涅盘金身
-
第四百一十六章 修炼金身
- - -
第四百一十七章 麻烦上门
-
第四百一十八章 轰杀
-
第四百一十九章 前往阳城
-
第四百二十章 三人突破
- - -
第四百二十一章 交易场
-
第四百二十二章 晋牧
-
第四百二十三章 对战半步涅盘
-
第四百二十四章 震慑
- - -
第四百二十五章 名誉扫地
-
第四百二十六章 凑齐
-
第四百二十七章 天符灵树
-
第四百二十八章 净化血灵傀
- - -
第四百二十九章 动身
-
第四百三十章 进入雷岩山脉
-
第四百三十一章 再遇妖潮
-
第四百三十二章 斩杀
- - -
第四百三十三章 狠毒
-
第四百三十四章 收割
-
第四百三十五章 再度提升
-
第四百三十六章 讨债
- - -
第四百三十七章 斩杀晋牧
-
第四百三十八章 凌志,柳元
-
第四百三十九章 雷岩谷
-
第四百四十章 两大高级王朝
- - -
第四百四十一章 赌约
-
第四百四十二章 承让
-
第四百四十三章 暗流涌动
-
第四百四十四章 石殿
- - -
第四百四十五章 树纹符文
-
第四百四十六章 底牌
-
第四百四十七章 主殿
-
第四百四十八章 机关
- - -
第四百四十九章 石像
-
第四百五十章 红衣女子
-
第四百五十一章 穆红绫
-
第四百五十二章 变故
- - -
第四百五十三章 夺舍
-
第四百五十四章 天符师
-
第四百五十五章 李盘
-
第四百五十六章 现身
- - -
第四百五十七章 天符师的强大
-
第四百五十八章 杀手
-
第四百五十九章 麻烦
-
第四百六十章 算计
- - -
第四百六十一章 来临
-
第四百六十二章 交手
-
第四百六十三章 雷蛇
-
第四百六十四章 吞噬之界
- - -
第四百六十五章 肥羊
-
第四百六十六章 绑架勒索
-
第四百六十七章 冲击半步涅盘
-
第四百六十八章 取丹
- - -
第四百六十九章 石轩
-
第四百七十章 一元涅盘
-
第四百七十一章 状况
-
第四百七十二章 体内阵法
- - -
第四百七十三章 乾坤古阵
-
第四百七十四章 改变血脉
-
第四百七十五章 远古之地
-
第四百七十六章 大力裂地虎
- - -
第四百七十七章 激斗
-
第四百七十八章 出手
-
第四百七十九章 惊退
-
第四百八十章 虎骨到手
- - -
第四百八十一章 融合虎骨
-
第四百八十二章 脱胎换骨的小炎
-
第四百八十三章 三兄弟
-
第四百八十四章 远古之殿
- - -
第四百八十五章 大殿
-
第四百八十六章 小炎之威
-
第四百八十七章 精元大吞掌
-
第四百八十八章 立威
- - -
第四百八十九章 各方势力
-
第四百九十章 秘藏开启
-
第四百九十一章 金身舍利
-
第四百九十二章 丹河
- - -
第四百九十三章 天鹰王朝
-
第四百九十四章 冲击涅盘
-
第四百九十五章 双劫齐至
-
第四百九十六章 厚积薄发
- - -
第四百九十七章 实力大涨
-
第四百九十八章 摧枯拉朽
-
第四百九十九章 麻烦上门
-
第五百章 灵武学
- - -
第五百零一章 灵武学
-
第五百零二章 再遇
-
第五百零三章 宗派遗迹
-
第五百零四章 威慑力
- - -
第五百零五章 柳白
-
第五百零六章 天罡联盟
-
第五百零七章 涅盘焚天阵
-
第五百零八章 涅盘魔炎
- - -
第五百零九章 神秘人
-
第五百一十章 八极宗
-
第五百一十一章 魔龙犬
-
第五百一十二章 掌印,拳印,指洞
- - -
第五百一十三章 磅礴拳意
-
第五百一十四章 八极拳意
-
第五百一十五章 拳意之威
-
第五百一十六章 轰翻
- - -
第五百一十七章 四玄宗遗迹
-
第五百一十八章 丹场
-
第五百一十九章 丹室
-
第五百二十章 生死转轮丹
- - -
第五百二十一章 暴狼田震
-
第五百二十二章 小炎战田震
-
第五百二十三章 再遇
-
第五百二十四章 灵武学之斗
- - -
第五百二十五章 群雄
-
第五百二十六章 青铜大门
-
第五百二十七章 动用底牌
-
第五百二十八章 召唤远古天鳄
- - -
第五百二十九章 天鳄之威
-
第五百三十章 杀心
-
第五百三十一章 斩杀?
-
第五百三十二章 进入青铜大门
- - -
第五百三十三章
-
第五百三十四章
-
第五百三十五章
-
第五百三十六章 神秘的青雉
- - -
第五百三十七章 青天化龙诀
-
第五百三十八章 闭关
-
第五百三十九章 第三次涅盘劫
-
第五百四十章 对抗
- - -
第五百四十一章 涅盘火雷珠
-
第五百四十二章 真正的天妖貂
-
第五百四十三章 神秘老人
-
第五百四十四章 出关
- - -
第五百四十五章 大乾王朝
-
第五百四十六章 火将,山将
-
第五百四十七章 针锋相对
-
第五百四十八章 激战
- - -
第五百四十九章 青龙撕天手
-
第五百五十章 败二将
-
第五百五十一章 离去
-
第五百五十二章 被盯上了
- - -
第五百五十三章 小貂之力
-
第五百五十四章 夜遇
-
第五百五十五章 苏柔
-
第五百五十六章 出手
- - -
第五百五十七章 狠手段
-
第五百五十八章 同行
-
第五百五十九章 涅盘碑
-
第五百六十章 涅盘碑测试
- - -
第五百六十一章 常凌
-
第五百六十二章 包揽
-
第五百六十三章 万象拍卖会
-
第五百六十四章 罗通
- - -
第五百六十五章 强势对碰
-
第五百六十六章 青龙指
-
第五百六十七章 风雨欲来
-
第五百六十八章 四大超级王朝
- - -
第五百六十九章 拍卖会开始
-
第五百七十章 平衡灵果
-
第五百七十一章 天荒神牛
-
第五百七十二章 黑龙啸天印
- - -
第五百七十三章 财力比拼
-
第五百七十四章 最终归属
-
第五百七十五章 即将对决
-
第五百七十六章 百朝大战,开启!
- - -
第五百七十七章 对决
-
第五百七十八章 血战
-
第五百七十七章 天阶灵宝的威力
-
第五百七十八章 惊天动地
- - -
第五百七十九章 夺宝
-
第五百八十章 败亡
-
第五百八十一章 序幕拉开
-
第五百八十二章 死灵将
- - -
第五百八十三章 二段封印
-
第五百八十四章 苏醒
-
第五百八十五章 赶尽杀绝
-
第五百八十六章 实力精进
- - -
第五百八十七章 挺进深处
-
第五百八十八章 敢不敢
-
第五百八十九章 抗
-
第五百九十章 地煞联盟
- - -
第五百九十一章 对头
-
第五百九十二章 找上门来
-
第五百九十三章 萧山
-
第五百九十四章 龙灵战朱厌
- - -
第五百九十五章 横扫
-
第五百九十六章 再遇
-
第五百九十七章 蓝樱
-
第五百九十八章 七大超级宗派
- - -
第五百九十九章 合作
-
第六百章 应战
-
第六百零一章 宋家三魔
-
第六百零二章 怪异之举
- - -
第六百零三章 变态啊
-
第六百零四章 四元涅盘境
-
第六百零五章 饕鬄凶灵
-
第六百零六章 吞食与吞噬
- - -
第六百零七章 胜负
-
第六百零八章 你还有力量么?
-
第六百零九章 惨
-
第六百一十章 秦天
- - -
第六百一十一章 百朝山开
-
第六百一十二章 上山
-
第六百一十三章 八大超级宗派
-
第六百一十四章 熟面孔
- - -
第六百一十五章 涅盘金榜之战
-
第六百一十六章 合作
-
第六百一十七章 再战林琅天
-
第六百一十八章 聚武灵
- - -
第六百一十九章 手段尽施
-
第六百二十章 不动青龙钟
-
第六百二十一章 暴力
-
第六百二十二章 斩杀林琅天
- - -
第六百二十三章 斩尽杀绝
-
第六百二十四章 争夺空间
-
第六百二十五章 曹羽
-
第六百二十六章 出手
- - -
第六百二十七章 底牌尽出
-
第六百二十八章 叠加
-
第六百二十九章 三劫叠加
-
第六百三十章 雷劫憾阵
- - -
第六百三十一章 解困
-
第六百三十二章 再见绫清竹?
-
第六百三十三章 四年
-
第六百三十四章 来历
- - -
第六百三十五章 挑选宗派
-
第六百三十六章 加入道宗
-
第六百三十七章 百朝大战落幕
-
第六百三十八章 震撼大炎
- - -
第六百三十九章 道域,道宗!
-
第六百四十章 四大奇经
-
第六百四十一章 择殿
-
第六百四十二章 赏赐
- - -
第六百四十三章 指教
-
第六百四十四章 交手
-
第六百四十五章 荒刀
-
第六百四十六章
- - -
第六百四十七章 涅盘金气
-
第六百四十八章 丹河之底
-
第六百四十九章 动静
-
第六百五十章 轰动
- - -
第六百五十一章 龙元轮
-
第六百五十二章 五元涅盘劫
-
第六百五十三章 破河而出
-
第六百五十四章 亲传大弟子
- - -
第六百五十五章 蒋浩的阻拦
-
第六百五十六章 武学殿
-
第六百五十七章 荒决
-
第六百五十八章 荒石
- - -
第六百五十九章 凝聚荒种
-
第六百六十章 四座石碑
-
第六百六十一章 荒芜妖眼
-
第六百六十二章 荒
- - -
第六百六十三章 成功与否?
-
第六百六十四章 月比
-
第六百六十五章 激斗蒋浩
-
第六百六十六章 大星罡拳
- - -
第六百六十七章 妖眼之力
-
第六百六十八章 第五位亲传大弟子
-
第六百六十九章 月谈
-
第六百七十章 宁静
- - -
第六百七十一章 出宗
-
第六百七十二章 血岩地
-
第六百七十三章 仙元古树
-
第六百七十四章 猿王
- - -
第六百七十五章 斗猿王
-
第六百七十六章 斩杀
-
第六百七十七章 动静
-
第六百七十八章 不妙
- - -
第六百七十九章 麻烦的局面
-
第六百八十章 激斗屠夫
-
第六百八十一章 荒兽之灵
-
第六百八十二章 撤退
- - -
第六百八十三章 无相菩提音
-
第六百八十四章 救人
-
第六百八十五章 恩怨
-
第六百八十六章 血斗
- - -
第六百八十七章 吞噬仙元古果
-
第六百八十八章 斩杀苏雷
-
第六百八十九章 魔元咒体
-
第六百九十章 重伤
- - -
第六百九十一章 休养
-
第六百九十二章 应笑笑,青叶
-
第六百九十三章 道宗掌教
-
第六百九十四章 锁灵阵
- - -
第六百九十五章 再渡双劫
-
第六百九十六章 大荒芜碑
-
第六百九十七章 波动
-
第六百九十八章 荒芜
- - -
第六百九十九章 你病了
-
第七百章 破局
-
第七百零一章 未知生物
-
第七百零二章 参悟大荒芜经
- - -
第七百零三章 成功
-
第七百零四章 拜山
-
第七百零五章 洪崖洞
-
第七百零六章 暴力
- - -
第七百零七章 洪崖洞经
-
第七百零八章 强化的大荒囚天手
-
第七百零九章 弹琴的少女
-
第七百一十章 王阎
- - -
第七百一十一章 对恃
-
第七百一十二章 殿试开始
-
第七百一十三章 对战应欢欢
-
第七百一十四章 对手
- - -
第七百一十五章 顶尖交锋
-
第七百一十六章 激战
-
第七百一十七章 地龙封神印
-
第七百一十八章 龙翼
- - -
第七百一十九章 最顶尖的较量
-
第七百二十章 王阎对应笑笑
-
第七百二十一章 天皇经的对碰
-
第七百二十二章 出手
- - -
第七百二十三章 龙争虎斗
-
第七百二十四章 黑魔鉴VS大荒芜经
-
第七百二十五章 惨烈
-
第七百二十六章 胜败
- - -
第七百二十七章 指挥权归属
-
第七百二十八章 选宝
-
第七百二十九章 静止之牌
-
第七百三十章 妖灵烙印的动静
- - -
第七百三十一章 借琴
-
第七百三十二章 三人再聚
-
第七百三十三章 地心孕神涎
-
第七百三十四章 魔音山
- - -
第七百三十五章 突来之人
-
第七百三十六章 变故
-
第七百三十七章 局势转变
-
第七百三十八章 不过如此
- - -
第七百三十九章 斩草除根
-
第七百四十章 天妖貂的力量
-
第七百四十一章 收获颇丰
-
第七百四十二章 斗法
- - -
第七百四十三章 抹除
-
第七百四十四章 破丹孕神
-
第七百四十五章 实力大涨
-
第七百四十六章 轮回者
- - -
第七百四十七章 回宗
-
第七百四十八章 谈话
-
第七百四十九章 可还记得
-
第七百五十章 动身
- - -
第七百五十一章 异魔城
-
第七百五十二章 冲突
-
第七百五十三章 如鹰如隼
-
第七百五十四章 滑稽的交手
- - -
第七百五十五章 认识一下
-
第七百五十六章 五年后的见面
-
第七百五十七章 想死?
-
第七百五十八章 焚天古藏
- - -
第七百五十九章 妖孽云集
-
第七百六十章 针锋相对
-
第七百六十一章 异魔域,开启
-
第七百六十二章 生玄骨珠
- - -
第七百六十三章 变故
-
第七百六十四章 苦战魔尸
-
第七百六十五章 操控魔尸
-
第七百六十六章 骚扰
- - -
第七百六十七章 古藏信息
-
第七百六十八章 赶往古藏
-
第七百六十九章 借刀杀人
-
第七百七十章 兄妹相见
- - -
第七百七十一章 青檀
-
第七百七十二章 战书
-
第七百七十三章 激战雷千
-
第七百七十四章 雷帝典
- - -
第七百七十五章 逆转之威
-
第七百七十六章 对恃
-
第七百七十七章 焚天古藏开启
-
第七百七十八章 诡异的空间
- - -
第七百七十九章 中枢
-
第七百八十章 确定
-
第七百八十一章 变故
-
第七百八十二章 鼎炉
- - -
第七百八十三章 赤袍人
-
第七百八十四章 赤袍对黑雾
-
第七百八十五章 镇压
-
第七百八十六章 焚天
- - -
第七百八十七章 炼化
-
第七百八十八章 八元涅盘境
-
第七百八十九章 太清仙池
-
第七百九十章 杨氏兄弟
- - -
第七百九十一章 武帝典
-
第七百九十二章 焚天阵之威
-
第七百九十三章 武帝
-
第七百九十四章 池底变故
- - -
第七百九十五章 后果
-
第七百九十六章 动手
-
第七百九十七章 开战
-
第七百九十八章 恩怨
- - -
第七百九十九章 弟子之战
-
第八百章 混战
-
第八百零一章 元苍的灵印
-
第八百零二章 两女联手
- - -
第八百零三章 惨烈
-
第八百零四章 能耐
-
第八百零五章 顶尖交锋
-
第八百零六章 再现大荒芜经
- - -
第八百零七章 激斗元苍
-
第八百零八章 焚天鼎之威
-
第八百零九章 局势转换
-
第八百一十章 疯子
- - -
第八百一十一章 荒芜石珠
-
第八百一十二章 惨胜
-
第八百一十三章 落幕
-
第八百一十四章 震动
- - -
第八百一十五章 归来
-
第八百一十六章 纠纷
-
第八百一十七章 再聚首
-
第八百一十八章 小貂之威
- - -
第八百一十九章 以一敌六
-
第八百二十章 人元子
-
第八百二十一章 惨烈
-
第八百二十二章 惨败的三兄弟
- - -
第八百二十三章 一份情
-
第八百二十四章 顶尖强者云集
-
第八百二十五章 退宗
-
第八百二十七章(上) 拼命
- - -
第八百二十七章(下) 空间挪移
-
第八百二十八章
-
第八百二十九章 逃离
-
第八百三十章 他会回来的
- - -
第八百三十一章 陌生的地方
-
第八百三十二章 乱魔海,天风海域
-
第八百三十三章 生生玄灵果
-
第八百三十四章 玄元丹
- - -
第八百三十五章 夜袭
-
第八百三十六章 仙符师
-
第八百三十七章 报酬
-
第八百三十八章 冲击九元涅盘境
- - -
第八百三十九章 玄灵山
-
第八百四十章 震慑
-
第八百四十一章 各方云集
-
第八百四十二章
- - -
第八百四十三章 青龙之力
-
第八百四十四章 三头魔蛟
-
第八百四十五章 混战
-
第八百四十六章 各施手段
- - -
第八百四十七章 到手
-
第八百四十八章 局势
-
第八百四十九章 吸进鼎炉
-
第八百五十章 峥嵘
- - -
第八百五十一章 抹杀
-
第八百五十二章 地心生灵浆
-
第八百五十三章 进湖
-
第八百五十四章 岩浆之后
- - -
第八百五十五章 虎口夺食
-
第八百五十六章 神秘的空间
-
第八百五十七章 令牌
-
第八百五十八章 外援
- - -
第八百五十九章 生玄境
-
第八百六十章 洪荒塔
-
第三百六十一章 动身
-
第三百六十二章 武会岛
- - -
第八百六十三章 争端
-
第八百六十四章 承让
-
第八百六十五章 油盐不进
-
第八百六十六章 合作
- - -
第八百六十七章 冤家路窄
-
第八百六十八章 武会
-
第八百六十九章 分配
-
第八百七十章 激斗苏岩
- - -
第八百七十一章 一掌
-
第八百七十二章 获胜
-
第八百七十三章 修罗模式
-
第八百七十四章 挑战
- - -
第八百七十五章 武帝怒
-
第八百七十六章 强势
-
第八百七十七章 初步接触
-
第八百七十八章 海
- - -
第八百七十九章 此路,不通
-
第八百八十章 青龙武装
-
第八百八十一章 青龙战修罗
-
第八百八十二章 修罗地煞狱
- - -
第八百八十三章 底牌频出
-
第八百八十四章 胜
-
第八百八十五章 落幕
-
第八百八十六章 进入洪荒塔
- - -
第八百八十七章 如海般的洪荒之气
-
第八百八十八章 获益匪浅
-
第八百八十九章 紫金之皮
-
第八百九十章 祖石之灵
- - -
第八百九十一章 麻烦上门
-
第八百九十二章 邪骨老人
-
第八百九十三章 设计
-
第八百九十四章 离开
- - -
第八百九十五章 斗邪骨
-
第八百九十六章 炎神古牌之力
-
第八百九十七章 重伤
-
第八百九十八章 血魔鲨族
- - -
第八百九十九章 青衣女童
-
第九百章 慕灵珊
-
第九百零一章 救人
-
第九百零二章 交手
- - -
第九百零三章 先下手
-
第九百零四章 屠戮
-
第九百零五章 海上激战
-
第九百零六章 魔鲨之牙
- - -
第九百零七章 斩草除根
-
第九百零八章 天商城
-
第九百零九章 唐冬灵
-
第九百一十章 炼制焚天门
- - -
第九百一十一章 完整的焚天鼎
-
第九百一十二章 天商拍卖会
-
第九百一十三章 对立
-
第九百一十四章 吞噬天尸
- - -
第九百一十五章 竞价
-
第九百一十六章 压箱底之物?
-
第九百一十七章 雷霆祖符的线索
-
第九百一十八章 争夺银塔
- - -
第九百一十九章 操控天尸
-
第九百二十章 好戏
-
第九百二十一章 变故
-
第九百二十二章 动手
- - -
第九百二十三章 焚天门之威
-
第九百二十四章 夺塔而走
-
第九百二十五章 追兵
-
第九百二十六章 天雷海域
- - -
第九百二十七章 途遇
-
第九百二十八章 无轩
-
第九百二十九章 两大转轮境
-
第九百三十章 薄礼
- - -
第九百三十一章 夜谈
-
第九百三十二章 抵达
-
第九百三十三章 算计
-
第九百三十四章 杀鸡儆猴
- - -
第九百三十五章 水深
-
第九百三十六章 进入天雷海域
-
第九百三十七章 凶险的天雷海域
-
第九百三十八章 洞府开启
- - -
第九百三十九章 雷光战场
-
第九百四十章 洞府之内
-
第九百四十一章 追杀
-
第九百四十二章 抹杀
- - -
第九百四十三章 神秘红袍人
-
第九百四十四章 雷霆之心
-
第九百四十五章 交手
-
第九百四十六章 热闹
- - -
第九百四十七章 雷岩沟壑
-
第九百四十八章 收取
-
第九百四十九章 湖底混战
-
第九百五十章 拖下水
- - -
第九百五十一章 激战庞昊
-
第九百五十二章 凶狠
-
第九百五十三章 异魔?
-
第九百五十四章 退避
- - -
第九百五十五章 帮
-
第九百五十六章 三大神物
-
第九百五十七章 抹除魔纹
-
第九百五十八章 左费
- - -
第九百五十九章 吸收
-
第九百六十章 雷殿
-
第九百六十一章 强者汇聚
-
第九百六十二章 九幽镇灵阵
- - -
第九百六十三章 引尸
-
第九百六十四章(上) 断手
-
第九百六十四章(下) 雷帝权杖
-
第九百六十五章 争夺
- - -
第九百六十六章 降服
-
第九百六十七章 上一届的元门三小王
-
第九百六十八章 摩罗
-
第九百六十九章 驱逐
- - -
第九百七十章 雷界
-
第九百七十一章 联手诛魔
-
第九百七十二章 三大祖符
-
第九百七十三章 抹杀异魔王
- - -
第九百七十四章 争执
-
第九百七十五章 倾尽手段
-
第九百七十六章 血拼到底
-
第九百七十七章 我赢了
- - -
第九百七十八章 诱饵
-
第九百七十九章 我自巍然
-
第九百八十章 十万雷霆铸雷身
-
第九百八十一章 炼化雷霆祖符
- - -
第九百八十二章 实力大进
-
第九百八十三章(上) 蹲守
-
第九百八十三章(下) 救人
-
第九百八十四章 诛杀
- - -
第九百八十五章 狠手段
-
第九百八十六章 魔迹
-
第九百八十七章 除魔
-
第九百八十八章 两大祖符
- - -
第九百八十九章 诛杀
-
第九百九十章 解决
-
第九百九十一章 震动
-
第九百九十二章 新秀榜
- - -
第九百九十三章 火炎城
-
第九百九十四章 熟人
-
第九百九十五章 冲突
-
第九百九十六章 交手
- - -
第九百九十七章 唐心莲
-
第九百九十八章 水深
-
第九百九十九章 报我的名
-
第一零零零章 大赛前的平静
- - -
第一千零一章 开启
-
第一千零二章 血之斩头卫
-
第一千零三章 一路向前
-
第一千零四章 遇上
- - -
第一千零五章 初次交手
-
第一千零六章 无量山
-
第一千零七章 登山
-
第一千零八章 鲨力
- - -
第一千零九章 登顶
-
第一千一十章 顶上之争
-
第一千一十一章 巅峰之战
-
第一千一十二章 隐忍待发
- - -
第一千一十三章 青锋出鞘
-
第一千一十四章 斗两魔
-
第一千一十五章 手段尽出
-
第一千一十六章 三重攻势
- - -
第一千一十七章 断臂
-
第一千一十八章 各施手段
-
第一千一十九章 三百道
-
第一千二十章 破镜
- - -
第一千二十一章 柔软
-
第一千二十二章 魔现
-
第一千二十三章 天冥王
-
第一千二十四章 青雉再现
- - -
第一千二十五章 炎神殿的统帅
-
第一千二十六章 恐怖的女孩
-
第一千二十七章
-
第一千二十八章 冲击
- - -
第一千二十九章 镇压
-
第一千三十章 灭王天盘
-
第一千三十一章 生死祖符
-
第一千三十二章 灭王天盘
- - -
第一千三十三章 魔狱
-
第一千三十四章 踪迹
-
第一千三十五章 死炎灵池
-
第一千三十六章 死气冲刷
- - -
第一千三十七章 最后一道
-
第一千三十八章 离开
-
第一千三十九章 暗云涌动
-
第一千四十章 兽战域
- - -
第一千四十一章 抢我的烤肉
-
第一千四十二章 带走
-
第一千四十三章 垫脚石
-
第一千四十四章 曹赢
- - -
第一千四十五章 露峥嵘
-
第一千四十六章 达到目的
-
第一千四十七章 抵达
-
第一千四十八章 九尾寨
- - -
第一千四十九章 秦刚与蒙山
-
第一千五十章 兄弟相聚
-
第一千五十一章 炎将
-
第一千五十二章 小炎的经历
- - -
第一千五十三章 相谈
-
第一千五十四章 秘辛
-
第一千五十五章 祖魂殿
-
第一千五十六章 九尾灵狐
- - -
第一千五十七章 传承
-
第一千五十八章 吞噬神殿
-
第一千五十九章 希望
-
第一千六十章 还有怀疑吗
- - -
第一千六十一章慑服
-
第一千六十二章 雷渊山脉
-
第一千六十三章 妖帅徐钟
-
第一千六十四章 各自的准备
- - -
第一千六十五章 斗妖帅
-
第一千六十六章 雷渊山之战
-
第一千六十七章 发狂
-
第一千六十八章 抹杀
- - -
第一千六十九章 易主
-
第一千七十章 精血传承
-
第一千七十一章 神物宝库
-
第一千七十二章 无间神狱盘
- - -
第一千七十三章 锤锻精神
-
第一千七十四章 血龙殿
-
第一千七十五章 两大统领
-
第一千七十六章 天龙妖帅
- - -
第一千七十七章 风波暂息
-
第一千七十八章 暴风雨前的宁静
-
第一千七十九章 神物山脉
-
第一千八十章 投鼠忌器
- - -
第一千八十一章 宝库出现
-
第一千八十二章 取宝
-
第一千八十三章 各施手段
-
第一千八十四章 玄天殿内
- - -
第一千八十五章 万魔蚀阵
-
第一千八十六章 联手
-
第一千八十七章 背水一战
-
第一千八十八章 战转轮
- - -
第一千八十九章 玄天殿
-
第一千九十章 再说一次
-
第一千九十一章 三兄弟,终聚首
-
第一千九十二章 霸道的天妖貂
- - -
第一千九十三章 小貂斗天龙
-
第一千九十四章 龙族
-
第一千九十五章 对恃
-
第一千九十六章 龙族的问题
- - -
第一千九十七章 前往龙族
-
第一千九十八章 龙族
-
第一千九十九章 龙族的麻烦
-
第一千一百章 棘手
- - -
第一千一百零一章 镇魔狱
-
第一千一百零二章 黑暗之主
-
第一千一百零三章 解决魔海
-
第一千一百零四章 名额
- - -
第一千一百零五章 严山
-
第一千一百零六章 龙骨
-
第一千一百零七章 化龙潭开启
-
第一千一百零八章 化龙骨
- - -
第一千一百零九章 埋骨之地
-
第一千一百一十章 远古龙骨
-
第一千一百一十一章 六指圣龙帝
-
第一千一百一十二章 帮手
- - -
第一千一百一十三章 巅峰交手
-
第一千一百一十四章 洪荒龙骨
-
第一千一百一十五章 刑罚长老
-
第一千一百一十六章 离开
- - -
第一千一百一十七章 邙山
-
第一千一百一十八章 联手
-
第一千一百一十九章 四象宫
-
第一千一百二十章 妖兽古原
- - -
第一千一百二十一章 天擂台
-
第一千一百二十二章 连败
-
第一千一百二十三章 神锤憾玄武
-
第一千一百二十四章 最后一场
- - -
第一千一百二十五章 激战罗通
-
第一千一百二十六章 九凤化生光
-
第一千一百二十七章 惨烈
-
第一千一百二十八章 结束
- - -
第一千一百二十九章 大整顿
-
第一千一百三十章 小貂的麻烦
-
第一千一百三十一章 魔狱再现
-
第一千一百三十二章 天洞
- - -
第一千一百三十三章 永恒幻魔花
-
第一千一百三十四章 苏醒
-
第一千一百三十五章 对决
-
第一千一百三十六章 孰强孰弱
- - -
第一千一百三十七章 昊九幽的手段
-
第一千一百三十八章 抓魔
-
第一千一百三十九章 异魔王再现
-
第一千一百四十章 大礼
- - -
第一千一百四十一章 出手
-
第一千一百四十二章 荒芜再现
-
第一千一百四十三章 永恒花魔身
-
第一千一百四十四章 祖符之手
- - -
第一千一百四十五章 解局
-
第一千一百四十六章 冲击符宗
-
第一千一百四十七章 炼狱
-
第一千一百四十八章 化茧
- - -
第一千一百四十九章 守关者
-
第一千一百五十章 晋入符宗
-
第一千一百五十一章 出关
-
第一千一百五十二章 投诚
- - -
第一千一百五十三章 打压
-
第一千一百五十四章 一剑败双雄
-
第一千一百五十五章 震慑
-
第一千一百五十六章 妖域震动
- - -
第一千一百五十七章 群强云集
-
第一千一百五十八章 柳青
-
第一千一百五十九章 鲲灵
-
第一千一百六十章 进荒原
- - -
第一千一百六十一章 抵达
-
第一千一百六十二章 黑暗圣虎
-
第一千一百六十三章 三大虎族
-
第一千一百六十四章 以一敌二
- - -
第一千一百六十五章 孤峰上的大殿
-
第一千一百六十六章 神秘黑影
-
第一千一百六十七章 闯关
-
第一千一百六十八章 九峰
- - -
第一千一百六十九章 黑暗之中
-
第一千一百七十章 两股吞噬之力
-
第一千一百七十一章 吞噬之主
-
第一千一百七十二章 传承之秘
- - -
第一千一百七十三章 借身斩魔
-
第一千一百七十四章 恐怖的吞噬之主
-
第一千一百七十五章 福泽
-
第一千一百七十六章 三重轮回劫
- - -
第一千一百七十七章 轮回之海
-
第一千一百七十八章 战争
-
第一千一百七十九章 杀回去
-
第一千一百八十章 调遣人马
- - -
第一千一百八十一章 重回东玄域
-
第一千一百八十二章 东玄域的局势
-
第一千一百八十三章 归来
-
第一千一百八十四章 打
- - -
第一千一百八十五章 传奇
-
第一千一百八十六章 再见应欢欢
-
第一千一百八十七章 壮我道宗!
-
第一千一百八十八章 归宗
- - -
第一千一百八十九章 表现
-
第一千一百九十章 进碑
-
第一千一百九十一章 联手斩魔
-
第一千一百九十二章 撞车
- - -
第一千一百九十三章 判断失误
-
第一千一百九十四章 太清宫之难
-
第一千一百九十五章 太上宫
-
第一千一百九十六章 孤寂芳影,一人迎敌
- - -
第一千一百九十七章 没人能伤你
-
第一千一百九十八章 小心点
-
第一千一百九十九章 讨债开始
-
第一千两百章 显威
- - -
第一千两百零一章 第八道祖符
-
第一千两百零二章 空间祖符
-
第一千两百零三章 诛元盟
-
第一千两百零四章 汇聚,决战来临
- - -
第一千两百零五章 元门之外
-
第一千两百零六章 周通
-
第一千两百零七章 林动vs周通
-
第一千两百零八章 魔皇锁
- - -
第一千两百零九章 解救
-
第一千两百一十章 盛大魔宴
-
第一千两百一十一章 龙,虎,貂
-
第一千两百一十二章 斗魔
- - -
第一千两百一十三章 雪耻之战
-
第一千两百一十四章 祖符之眼
-
第一千两百一十五章 斩杀三巨头
-
第一千两百一十六章 两女联手
- - -
第一千两百一十七章 四王殿
-
第一千两百一十八章 炎主
-
第一千两百一十九章 树静风止
-
第一千两百二十章 大符宗
- - -
第一千两百二十一章 怒斗炎主
-
第一千两百二十二章 大雪初晴
-
第一千两百二十三章 相谈
-
第一千两百二十四章 太上感应诀
- - -
第一千两百二十五章 山顶之谈
-
第一千两百二十六章 千万大山
-
第一千两百二十七章 再遇辰傀
-
第一千两百二十八章 青檀之事
- - -
第一千两百二十九章 黑暗之城
-
第一千两百三十章 逼宫
-
第一千两百三十一章 相见
-
第一千两百三十二章 显威
- - -
第一千两百三十三章 镰灵
-
第一千两百三十四章 手段
-
第一千两百三十五章 魔袭而来
-
第一千两百三十六章 七王殿
- - -
第一千两百三十七章 魔皇甲
-
第一千两百三十八章 力战
-
第一千两百三十九章 修炼之法
-
第一千两百四十章 感应
- - -
第一千两百四十一章 雷弓黑箭
-
第一千两百四十二章 雷主
-
第一千两百四十三章 魔狱之事
-
第一千两百四十四章 再回异魔域
- - -
第一千两百四十五章 唤醒焚天
-
第一千两百四十六章 相聚
-
第一千两百四十七章 安宁
-
第一千两百四十八章 鹰宗
- - -
第一千两百四十九章 故人
-
第一千两百五十章 碑中之魔
-
第一千两百五十一章 九王殿
-
第一千两百五十二章 诛杀
- - -
第一千两百五十三章 差之丝毫
-
第一千两百五十四章 回宗
-
第一千两百五十五章 师徒相聚
-
第一千两百五十六章 位面裂缝
- - -
第一千两百五十七章 晋入轮回
-
第一千两百五十八章 动荡之始
-
第一千两百五十九章 再至乱魔海
-
第一千两百六十章 万魔围岛
- - -
第一千两百六十一章 熟人
-
第一千两百六十二章 洪荒之主
-
第一千两百六十三章 大战来临
-
第一千两百六十四章 再遇七王殿
- - -
第一千两百六十五章 血战
-
第一千两百六十六章 魔皇虚影
-
第一千两百六十七章 混沌之箭
-
第一千两百六十八章 应欢欢出手
- - -
第一千两百六十九章 空间之主
-
第一千两百七十章 诸强汇聚
-
第一千两百七十一章 巅峰对恃
-
第一千两百七十二章 祖宫阙
- - -
第一千两百七十三章 联盟
-
第一千两百七十四章 大殿盛宴
-
第一千两百七十五章 大会
-
第一千两百七十六章 开启祖宫阙
- - -
第一千两百七十七章 修炼之路
-
第一千两百七十八章 动荡
-
第一千两百七十九章 凝聚神宫
-
第一千两百八十章 三大联盟
- - -
第一千两百八十一章 天王殿
-
第一千两百八十二章 一瞬十年
-
第一千两百八十三章 青雉战魔
-
第一千两百八十四章 平定乱魔海
- - -
第一千两百八十五章 四玄域联盟
-
第一千两百八十六章 争吵
-
第一千两百八十七章 生死之主
-
第一千两百八十八章 大军齐聚
- - -
第一千两百八十九章 进攻西玄域
-
第一千两百九十章 西玄大沙漠
-
第一千两百九十一章 天地大战
-
第一千两百九十二章 激斗三王殿
- - -
第一千两百九十三章 魔皇之像
-
第一千两百九十四章 魔皇之手
-
第一千两百九十五章 自己来守护
-
第一千两百九十六章 位面裂缝
- - -
第一千两百九十七章 后手
-
第一千两百九十八章 抉择
-
第一千两百九十九章 青阳镇
-
第一千三百章 一年
- - -
第一千三百零一章 成功与否
-
第一千三百零二章 祈愿
-
第一千三百零三章 轮回
-
第一千三百零四章 封印破碎
- - -
第一千三百零五章 晋入祖境
-
第一千三百零六章 最后一战
-
第一千三百零七章 我要把你找回来
-
结局感言以及新书
- - -
大结局活动,1744,欢迎大家。
-
应欢欢篇
-
绫清竹篇
-
新书大主宰已发。
-
-
-
- -
- - - - """ - res = AttrField("href", css_select='#list > dl > dd:nth-child(1) > a').extract(html) - print(res) + pass diff --git a/smart/log.py b/smart/log.py index a9a1230..1d6ad52 100644 --- a/smart/log.py +++ b/smart/log.py @@ -14,7 +14,7 @@ from smart.setting import gloable_setting_dict LOG_FORMAT = "process %(process)d|thread %(threadName)s|%(asctime)s|%(filename)s|%(funcName)s|line:%(lineno)d|%(levelname)s: %(message)s" -CONSOLE_LOG_FORMAT = "%(colorName)sprocess %(process)d|thread %(threadName)s|%(asctime)s|%(filename)s|%(funcName)s|line:%(lineno)d|%(levelname)s: %(message)s %(colorNameSuffix)s" +CONSOLE_LOG_FORMAT = f"%(colorName)s{LOG_FORMAT} %(colorNameSuffix)s" PRINT_EXCEPTION_DETAILS = True @@ -78,10 +78,9 @@ class MyStreamHandler(logging.StreamHandler): def emit(self, record): if record.levelname in ["ERROR", "CRITICAL"]: record.colorName = "\033[0;31m " - record.colorNameSuffix = " \033[0m" else: record.colorName = "\033[0;34m " - record.colorNameSuffix = " \033[0m" + record.colorNameSuffix = " \033[0m" super().emit(record) diff --git a/smart/request.py b/smart/request.py index 0c0a1a6..f94ef51 100644 --- a/smart/request.py +++ b/smart/request.py @@ -5,8 +5,8 @@ # Date: 2020/12/21 # Desc: there is a python file description # ------------------------------------------------------------------ -from dataclasses import dataclass, field, InitVar -from typing import Callable +from dataclasses import dataclass, InitVar +from typing import Callable, Any from smart.tool import is_valid_url @@ -15,6 +15,7 @@ class Request: url: InitVar[str] callback: Callable = None + session: Any = None method: str = 'get' timeout: float = None # if None will auto detect encoding @@ -33,6 +34,10 @@ class Request: _retry: int = 0 def __post_init__(self, url): + if url is None or url == '': + raise ValueError("request url can not be empty ") + if url and not (url.startswith("http") or url.startswith("ftp")): + url = "http://" + url if is_valid_url(url): self.url = url else: diff --git a/smart/response.py b/smart/response.py index bd5a468..1d9ee55 100644 --- a/smart/response.py +++ b/smart/response.py @@ -84,12 +84,23 @@ def selector(self) -> Selector: def content(self) -> bytes: return self.body + @property + def content_type(self) -> Optional[str]: + if self.headers: + for key in self.headers.keys(): + if "content_type" == key.lower(): + return self.headers.get(key) + return None + @property def text(self) -> Optional[str]: if not self.body: return None # if request encoding is none and then auto detect encoding self.request.encoding = self.encoding or cchardet.detect(self.body)["encoding"] + if self.request.encoding is None: + raise UnicodeDecodeError( + "body can not detect an encoding,it may be a binary data or you can set request.encoding to try it ") # minimum possible may be UnicodeDecodeError return self.body.decode(self.encoding) diff --git a/smart/runer.py b/smart/runer.py index b77c94b..b024d43 100644 --- a/smart/runer.py +++ b/smart/runer.py @@ -23,6 +23,14 @@ from smart.spider import Spider from smart.tool import is_valid_url +try: + # uvloop performance is better on linux.. + import uvloop + + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) +except ImportError: + pass + class CrawStater: __version = "0.1.0" @@ -32,8 +40,6 @@ def __init__(self, loop=None): # avoid a certain extent: too many files error loop = loop or asyncio.ProactorEventLoop() else: - # uvloop performance is better on linux.. - # todo use uvloop self.loop = loop or asyncio.new_event_loop() thread_pool_max_size = gloable_setting_dict.get( "thread_pool_max_size", 30) @@ -91,7 +97,6 @@ def run(self, spider_module: str, spider_names: List[str] = [], middlewire: Midd self.spider_names.append(_spider.name) self._run() - def stop(self): self.log.info(f'warning stop be called, {",".join(self.spider_names)} will stop ') for core in self.cores: @@ -128,8 +133,7 @@ def _run(self): except BaseException as e3: self.log.error(f" in loop, occured BaseException e {e3} ", exc_info=True) - self.log.info(f'craw succeed {",".join(self.spider_names)} ended.. it cost {round(time.time() - start,3)} s') - + self.log.info(f'craw succeed {",".join(self.spider_names)} ended.. it cost {round(time.time() - start, 3)} s') def _print_info(self): self.log.info("good luck!") @@ -148,9 +152,8 @@ def _print_info(self): ) self.log.info(" \r\n smart-spider-framework" f"\r\n os: {sys.platform}" - " \r\n author: liangbaikai" - " \r\n emial:1144388620@qq.com" - " \r\n version: 0.1.0" + " \r\n author: liangbaikai<1144388620@qq.com>" + f" \r\n version: {self.__version}" " \r\n proverbs: whatever is worth doing is worth doing well." ) diff --git a/smart/setting.py b/smart/setting.py index 15f0af3..7eb2920 100644 --- a/smart/setting.py +++ b/smart/setting.py @@ -17,6 +17,10 @@ "req_max_retry": 3, # 默认请求头 "default_headers": { + "Accept": "*/*;", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", + # 百度搜索引擎爬虫ua "user-agent": "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)" }, # 请求url 去重处理器 @@ -32,11 +36,11 @@ "thread_pool_max_size": 50, # 根据响应的状态码 忽略以下响应 "ignore_response_codes": [401, 403, 404, 405, 500, 502, 504], - # 网络是否畅通检查地址 + # 启动时网络是否畅通检查地址 "net_healthy_check_url": "https://www.baidu.com", # log level "log_level": "info", "log_name": "smart-spider", - "log_path": "D://test//smart.log", + "log_path": ".logs/smart.log", "is_write_to_file": False, } diff --git a/smart/tool.py b/smart/tool.py index b56dc90..3002fae 100644 --- a/smart/tool.py +++ b/smart/tool.py @@ -1,573 +1,20 @@ -# -*- coding: utf-8 -*- -""" -Created on 2018-09-06 14:21 ---------- -@summary: 工具 ---------- -@author: Boris -@email: boris@bzkj.tech -""" -import calendar -import codecs -import configparser # 读配置文件的 -import datetime -import functools -import hashlib -import html -import json -import os -import pickle -import random import re import socket -import ssl -import string -import sys -import time -import traceback import urllib -import urllib.parse -import uuid -from hashlib import md5 -from pprint import pformat -from pprint import pprint -from urllib import request -from urllib.parse import urljoin -# import execjs # pip install PyExecJS -# import redis -import requests -import six -from requests.cookies import RequestsCookieJar -from w3lib.url import canonicalize_url as sort_url +RE_COMPILE = re.compile("(^https?:/{2}\w.+$)|(ftp://)") -# import spider.setting as setting -from smart.log import log -os.environ["EXECJS_RUNTIME"] = "Node" # 设置使用node执行js - -# 全局取消ssl证书验证 -ssl._create_default_https_context = ssl._create_unverified_context - -TIME_OUT = 30 -TIMER_TIME = 5 - -redisdb = None - -CAMELCASE_INVALID_CHARS = re.compile(r'[^a-zA-Z\d]') - - -# def get_redisdb(): -# global redisdb -# if not redisdb: -# ip, port = setting.REDISDB_IP_PORTS.split(":") -# redisdb = redis.Redis( -# host=ip, -# port=port, -# db=setting.REDISDB_DB, -# password=setting.REDISDB_USER_PASS, -# decode_responses=True, -# ) # redis默认端口是6379 -# return redisdb - - -# 装饰器 -def log_function_time(func): - try: - - @functools.wraps(func) # 将函数的原来属性付给新函数 - def calculate_time(*args, **kw): - began_time = time.time() - callfunc = func(*args, **kw) - end_time = time.time() - log.debug(func.__name__ + " run time = " + str(end_time - began_time)) - return callfunc - - return calculate_time - except: - log.debug("求取时间无效 因为函数参数不符") - return func - - -def run_safe_model(module_name): - def inner_run_safe_model(func): - try: - - @functools.wraps(func) # 将函数的原来属性付给新函数 - def run_func(*args, **kw): - callfunc = None - try: - callfunc = func(*args, **kw) - except Exception as e: - log.error(module_name + ": " + func.__name__ + " - " + str(e)) - traceback.print_exc() - return callfunc - - return run_func - except Exception as e: - log.error(module_name + ": " + func.__name__ + " - " + str(e)) - traceback.print_exc() - return func - - return inner_run_safe_model - - -########################【网页解析相关】############################### - - -# @log_function_time -def get_html_by_requests( - url, headers=None, code="utf-8", data=None, proxies={}, with_response=False -): - html = "" - r = None - try: - if data: - r = requests.post( - url, headers=headers, timeout=TIME_OUT, data=data, proxies=proxies - ) - else: - r = requests.get(url, headers=headers, timeout=TIME_OUT, proxies=proxies) - - if code: - r.encoding = code - html = r.text - - except Exception as e: - log.error(e) - finally: - r and r.close() - - if with_response: - return html, r - else: - return html - - -def get_json_by_requests( - url, - params=None, - headers=None, - data=None, - proxies={}, - with_response=False, - cookies=None, -): - json = {} - response = None - try: - # response = requests.get(url, params = params) - if data: - response = requests.post( - url, - headers=headers, - data=data, - params=params, - timeout=TIME_OUT, - proxies=proxies, - cookies=cookies, - ) - else: - response = requests.get( - url, - headers=headers, - params=params, - timeout=TIME_OUT, - proxies=proxies, - cookies=cookies, - ) - response.encoding = "utf-8" - json = response.json() - except Exception as e: - log.error(e) - finally: - response and response.close() - - if with_response: - return json, response - else: - return json - - -def get_cookies(response): - cookies = requests.utils.dict_from_cookiejar(response.cookies) - return cookies - - -def get_cookies_jar(cookies): - """ - @summary: 适用于selenium生成的cookies转requests的cookies - requests.get(xxx, cookies=jar) - 参考:https://www.cnblogs.com/small-bud/p/9064674.html - - --------- - @param cookies: [{},{}] - --------- - @result: cookie jar - """ - - cookie_jar = RequestsCookieJar() - for cookie in cookies: - cookie_jar.set(cookie["name"], cookie["value"]) - - return cookie_jar - - -def get_cookies_from_selenium_cookie(cookies): - """ - @summary: 适用于selenium生成的cookies转requests的cookies - requests.get(xxx, cookies=jar) - 参考:https://www.cnblogs.com/small-bud/p/9064674.html - - --------- - @param cookies: [{},{}] - --------- - @result: cookie jar - """ - - cookie_dict = {} - for cookie in cookies: - if cookie.get("name"): - cookie_dict[cookie["name"]] = cookie["value"] - - return cookie_dict - - -def cookiesjar2str(cookies): - str_cookie = "" - for k, v in requests.utils.dict_from_cookiejar(cookies).items(): - str_cookie += k - str_cookie += "=" - str_cookie += v - str_cookie += "; " - return str_cookie - - -def cookies2str(cookies): - str_cookie = "" - for k, v in cookies.items(): - str_cookie += k - str_cookie += "=" - str_cookie += v - str_cookie += "; " - return str_cookie - - -def get_urls( - html, - stop_urls=( - "javascript", - "+", - ".css", - ".js", - ".rar", - ".xls", - ".exe", - ".apk", - ".doc", - ".jpg", - ".png", - ".flv", - ".mp4", - ), -): - # 不匹配javascript、 +、 # 这样的url - regex = r'>> string_camelcase('lost-pound') - 'LostPound' - - >>> string_camelcase('missing_images') - 'MissingImages' - - """ - return CAMELCASE_INVALID_CHARS.sub('', string.title()) - - -def get_full_url(root_url, sub_url): - """ - @summary: 得到完整的ur - --------- - @param root_url: 根url (网页的url) - @param sub_url: 子url (带有相对路径的 可以拼接成完整的) - --------- - @result: 返回完整的url - """ - - return urljoin(root_url, sub_url) - - -def joint_url(url, params): - # param_str = "?" - # for key, value in params.items(): - # value = isinstance(value, str) and value or str(value) - # param_str += key + "=" + value + "&" - # - # return url + param_str[:-1] - - if not params: - return url - - params = urlencode(params) - separator = "?" if "?" not in url else "&" - return url + separator + params - - -def canonicalize_url(url): - """ - url 归一化 会参数排序 及去掉锚点 - """ - return sort_url(url) - - -def get_url_md5(url): - url = canonicalize_url(url) - url = re.sub("^http://", "https://", url) - return get_md5(url) - - -def fit_url(urls, identis): - identis = isinstance(identis, str) and [identis] or identis - fit_urls = [] - for link in urls: - for identi in identis: - if identi in link: - fit_urls.append(link) - return list(set(fit_urls)) - - -def get_param(url, key): - params = url.split("?")[-1].split("&") - for param in params: - key_value = param.split("=", 1) - if key == key_value[0]: - return key_value[1] - return None - - -def urlencode(params): - """ - 字典类型的参数转为字符串 - @param params: - { - 'a': 1, - 'b': 2 - } - @return: a=1&b=2 - """ - return urllib.parse.urlencode(params) - - -def urldecode(url): - """ - 将字符串类型的参数转为json - @param url: xxx?a=1&b=2 - @return: - { - 'a': 1, - 'b': 2 - } - """ - params_json = {} - params = url.split("?")[-1].split("&") - for param in params: - key, value = param.split("=") - params_json[key] = unquote_url(value) - - return params_json - - -def unquote_url(url, encoding="utf-8"): - """ - @summary: 将url解码 - --------- - @param url: - --------- - @result: - """ - - return urllib.parse.unquote(url, encoding=encoding) - - -def quote_url(url, encoding="utf-8"): - """ - @summary: 将url编码 编码意思http://www.w3school.com.cn/tags/html_ref_urlencode.html - --------- - @param url: - --------- - @result: - """ - - return urllib.parse.quote(url, safe="%;/?:@&=+$,", encoding=encoding) - - -def quote_chinese_word(text, encoding="utf-8"): - def quote_chinese_word_func(text): - chinese_word = text.group(0) - return urllib.parse.quote(chinese_word, encoding=encoding) - - return re.sub("([\u4e00-\u9fa5]+)", quote_chinese_word_func, text, flags=re.S) - - -def unescape(str): - """ - 反转译 - """ - return html.unescape(str) - - -def excape(str): - """ - 转译 - """ - return html.escape(str) - - -_regexs = {} - - -# @log_function_time -def get_info(html, regexs, allow_repeat=True, fetch_one=False, split=None): - regexs = isinstance(regexs, str) and [regexs] or regexs - - infos = [] - for regex in regexs: - if regex == "": - continue - - if regex not in _regexs.keys(): - _regexs[regex] = re.compile(regex, re.S) - - if fetch_one: - infos = _regexs[regex].search(html) - if infos: - infos = infos.groups() - else: - continue - else: - infos = _regexs[regex].findall(str(html)) - - if len(infos) > 0: - # print(regex) - break - - if fetch_one: - infos = infos if infos else ("",) - return infos if len(infos) > 1 else infos[0] - else: - infos = allow_repeat and infos or sorted(set(infos), key=infos.index) - infos = split.join(infos) if split else infos - return infos - - -def table_json(table, save_one_blank=True): - """ - 将表格转为json 适应于 key:value 在一行类的表格 - @param table: 使用selector封装后的具有xpath的selector - @param save_one_blank: 保留一个空白符 - @return: - """ - data = {} - - trs = table.xpath(".//tr") - for tr in trs: - tds = tr.xpath("./td|./th") - - for i in range(0, len(tds), 2): - if i + 1 > len(tds) - 1: - break - - key = tds[i].xpath("string(.)").extract_first(default="").strip() - value = tds[i + 1].xpath("string(.)").extract_first(default="").strip() - value = replace_str(value, "[\f\n\r\t\v]", "") - value = replace_str(value, " +", " " if save_one_blank else "") - - if key: - data[key] = value - - return data - - -def get_table_row_data(table): - """ - 获取表格里每一行数据 - @param table: 使用selector封装后的具有xpath的selector - @return: [[],[]..] - """ - - datas = [] - rows = table.xpath(".//tr") - for row in rows: - cols = row.xpath("./td|./th") - row_datas = [] - for col in cols: - data = col.xpath("string(.)").extract_first(default="").strip() - row_datas.append(data) - datas.append(row_datas) - - return datas - - -def rows2json(rows, keys=None): - """ - 将行数据转为json - @param rows: 每一行的数据 - @param keys: json的key,空时将rows的第一行作为key - @return: - """ - data_start_pos = 0 if keys else 1 - datas = [] - keys = keys or rows[0] - for values in rows[data_start_pos:]: - datas.append(dict(zip(keys, values))) - - return datas - - -def get_form_data(form): +def is_valid_url(url): """ - 提取form中提交的数据 - :param form: 使用selector封装后的具有xpath的selector + 验证url是否合法 + :param url: :return: """ - data = {} - inputs = form.xpath(".//input") - for input in inputs: - name = input.xpath("./@name").extract_first() - value = input.xpath("./@value").extract_first() - if name: - data[name] = value - - return data - - -# mac上不好使 -# def get_domain(url): -# domain = '' -# try: -# domain = get_tld(url) -# except Exception as e: -# log.debug(e) -# return domain + if RE_COMPILE.match(url): + return True + else: + return False def get_domain(url): @@ -601,1595 +48,3 @@ def get_localhost_ip(): s.close() return ip - - -def ip_to_num(ip): - import struct - - ip_num = socket.ntohl(struct.unpack("I", socket.inet_aton(str(ip)))[0]) - return ip_num - - -def is_valid_proxy(proxy, check_url=None): - """ - 检验代理是否有效 - @param proxy: xxx.xxx.xxx:xxx - @param check_url: 利用目标网站检查,目标网站url。默认为None, 使用代理服务器的socket检查, 但不能排除Connection closed by foreign host - @return: True / False - """ - is_valid = False - - if check_url: - proxies = {"http": f"http://{proxy}", "https": f"https://{proxy}"} - headers = { - "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36" - } - response = None - try: - response = requests.get( - check_url, headers=headers, proxies=proxies, stream=True, timeout=20 - ) - is_valid = True - - except Exception as e: - log.error("check proxy failed: {} {}".format(e, proxy)) - - finally: - if response: - response.close() - - else: - ip, port = proxy.split(":") - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sk: - sk.settimeout(7) - try: - sk.connect((ip, int(port))) # 检查代理服务器是否开着 - is_valid = True - - except Exception as e: - log.error("check proxy failed: {} {}:{}".format(e, ip, port)) - - return is_valid - - -def is_valid_url(url): - """ - 验证url是否合法 - :param url: - :return: - """ - if re.match(r"(^https?:/{2}\w.+$)|(ftp://)", url): - return True - else: - return False - - -def get_text(soup, *args): - try: - return soup.get_text() - except Exception as e: - log.error(e) - return "" - - -def del_html_tag(content, except_line_break=False, save_img=False, white_replaced=""): - """ - 删除html标签 - @param content: html内容 - @param except_line_break: 保留p标签 - @param save_img: 保留图片 - @param white_replaced: 空白符替换 - @return: - """ - content = replace_str(content, "(?i)") # (?)忽略大小写 - content = replace_str(content, "(?i)") - content = replace_str(content, "") - content = replace_str( - content, "(?!&[a-z]+=)&[a-z]+;?" - ) # 干掉 等无用的字符 但&xxx= 这种表示参数的除外 - if except_line_break: - content = content.replace("

", "/p") - content = replace_str(content, "<[^p].*?>") - content = content.replace("/p", "

") - content = replace_str(content, "[ \f\r\t\v]") - - elif save_img: - content = replace_str(content, "(?!)<.+?>") # 替换掉除图片外的其他标签 - content = replace_str(content, "(?! +)\s+", "\n") # 保留空格 - content = content.strip() - - else: - content = replace_str(content, "<(.|\n)*?>") - content = replace_str(content, "\s", white_replaced) - content = content.strip() - - return content - - -def del_html_js_css(content): - content = replace_str(content, "(?i)") # (?)忽略大小写 - content = replace_str(content, "(?i)") - content = replace_str(content, "") - - return content - - -def is_have_chinese(content): - regex = "[\u4e00-\u9fa5]+" - chinese_word = get_info(content, regex) - return chinese_word and True or False - - -def is_have_english(content): - regex = "[a-zA-Z]+" - english_words = get_info(content, regex) - return english_words and True or False - - -def get_chinese_word(content): - regex = "[\u4e00-\u9fa5]+" - chinese_word = get_info(content, regex) - return chinese_word - - -def get_english_words(content): - regex = "[a-zA-Z]+" - english_words = get_info(content, regex) - return english_words or "" - - -################################################## -def get_json(json_str): - """ - @summary: 取json对象 - --------- - @param json_str: json格式的字符串 - --------- - @result: 返回json对象 - """ - - try: - return json.loads(json_str) if json_str else {} - except Exception as e1: - try: - json_str = json_str.strip() - json_str = json_str.replace("'", '"') - keys = get_info(json_str, "(\w+):") - for key in keys: - json_str = json_str.replace(key, '"%s"' % key) - - return json.loads(json_str) if json_str else {} - - except Exception as e2: - log.error( - """ - e1: %s - format json_str: %s - e2: %s - """ - % (e1, json_str, e2) - ) - - return {} - - -def jsonp2json(jsonp): - """ - 将jsonp转为json - @param jsonp: jQuery172013600082560040794_1553230569815({}) - @return: - """ - try: - return json.loads(re.match(".*?({.*}).*", jsonp, re.S).group(1)) - except: - raise ValueError("Invalid Input") - - -def dumps_json(json_, indent=4, sort_keys=False): - """ - @summary: 格式化json 用于打印 - --------- - @param json_: json格式的字符串或json对象 - --------- - @result: 格式化后的字符串 - """ - try: - if isinstance(json_, str): - json_ = get_json(json_) - - json_ = json.dumps( - json_, ensure_ascii=False, indent=indent, skipkeys=True, sort_keys=sort_keys - ) - - except Exception as e: - log.error(e) - json_ = pformat(json_) - - return json_ - - -def get_json_value(json_object, key): - """ - @summary: - --------- - @param json_object: json对象或json格式的字符串 - @param key: 建值 如果在多个层级目录下 可写 key1.key2 如{'key1':{'key2':3}} - --------- - @result: 返回对应的值,如果没有,返回'' - """ - current_key = "" - value = "" - try: - json_object = ( - isinstance(json_object, str) and get_json(json_object) or json_object - ) - - current_key = key.split(".")[0] - value = json_object[current_key] - - key = key[key.find(".") + 1:] - except Exception as e: - return value - - if key == current_key: - return value - else: - return get_json_value(value, key) - - -def get_all_keys(datas, depth=None, current_depth=0): - """ - @summary: 获取json李所有的key - --------- - @param datas: dict / list - @param depth: 字典key的层级 默认不限制层级 层级从1开始 - @param current_depth: 字典key的当前层级 不用传参 - --------- - @result: 返回json所有的key - """ - - keys = [] - if depth and current_depth >= depth: - return keys - - if isinstance(datas, list): - for data in datas: - keys.extend(get_all_keys(data, depth, current_depth=current_depth + 1)) - elif isinstance(datas, dict): - for key, value in datas.items(): - keys.append(key) - if isinstance(value, dict): - keys.extend(get_all_keys(value, depth, current_depth=current_depth + 1)) - - return keys - - -def to_chinese(unicode_str): - format_str = json.loads('{"chinese":"%s"}' % unicode_str) - return format_str["chinese"] - - -################################################## -def replace_str(source_str, regex, replace_str=""): - """ - @summary: 替换字符串 - --------- - @param source_str: 原字符串 - @param regex: 正则 - @param replace_str: 用什么来替换 默认为'' - --------- - @result: 返回替换后的字符串 - """ - str_info = re.compile(regex) - return str_info.sub(replace_str, source_str) - - -def del_redundant_blank_character(text): - """ - 删除冗余的空白符, 只保留一个 - :param text: - :return: - """ - return re.sub("\s+", " ", text) - - -################################################## -def get_conf_value(config_file, section, key): - cp = configparser.ConfigParser(allow_no_value=True) - with codecs.open(config_file, "r", encoding="utf-8") as f: - cp.read_file(f) - return cp.get(section, key) - - -def mkdir(path): - try: - os.makedirs(path) - except OSError as exc: # Python >2.5 - pass - - -def write_file(filename, content, mode="w", encoding="utf-8"): - """ - @summary: 写文件 - --------- - @param filename: 文件名(有路径) - @param content: 内容 - @param mode: 模式 w/w+ (覆盖/追加) - --------- - @result: - """ - - directory = os.path.dirname(filename) - mkdir(directory) - with open(filename, mode, encoding=encoding) as file: - file.writelines(content) - - -def read_file(filename, readlines=False, encoding="utf-8"): - """ - @summary: 读文件 - --------- - @param filename: 文件名(有路径) - @param readlines: 按行读取 (默认False) - --------- - @result: 按行读取返回List,否则返回字符串 - """ - - content = None - try: - with open(filename, "r", encoding=encoding) as file: - content = file.readlines() if readlines else file.read() - except Exception as e: - log.error(e) - - return content - - -def get_oss_file_list(oss_handler, prefix, date_range_min, date_range_max=None): - """ - 获取文件列表 - @param prefix: 路径前缀 如 data/car_service_line/yiche/yiche_serial_zongshu_info - @param date_range_min: 时间范围 最小值 日期分隔符为/ 如 2019/03/01 或 2019/03/01/00/00/00 - @param date_range_max: 时间范围 最大值 日期分隔符为/ 如 2019/03/01 或 2019/03/01/00/00/00 - @return: 每个文件路径 如 html/e_commerce_service_line/alibaba/alibaba_shop_info/2019/03/22/15/53/15/8ca8b9e4-4c77-11e9-9dee-acde48001122.json.snappy - """ - - # 计算时间范围 - date_range_max = date_range_max or date_range_min - date_format = "/".join( - ["%Y", "%m", "%d", "%H", "%M", "%S"][: date_range_min.count("/") + 1] - ) - time_interval = [ - {"days": 365}, - {"days": 31}, - {"days": 1}, - {"hours": 1}, - {"minutes": 1}, - {"seconds": 1}, - ][date_range_min.count("/")] - date_range = get_between_date( - date_range_min, date_range_max, date_format=date_format, **time_interval - ) - - for date in date_range: - file_folder_path = os.path.join(prefix, date) - objs = oss_handler.list(prefix=file_folder_path) - for obj in objs: - filename = obj.key - yield filename - - -def is_html(url): - if not url: - return False - - try: - content_type = request.urlopen(url).info().get("Content-Type", "") - - if "text/html" in content_type: - return True - else: - return False - except Exception as e: - log.error(e) - return False - - -def is_exist(file_path): - """ - @summary: 文件是否存在 - --------- - @param file_path: - --------- - @result: - """ - - return os.path.exists(file_path) - - -def download_file(url, base_path, filename="", call_func="", proxies=None, data=None): - file_path = base_path + filename - directory = os.path.dirname(file_path) - mkdir(directory) - - # 进度条 - def progress_callfunc(blocknum, blocksize, totalsize): - """回调函数 - @blocknum : 已经下载的数据块 - @blocksize : 数据块的大小 - @totalsize: 远程文件的大小 - """ - percent = 100.0 * blocknum * blocksize / totalsize - if percent > 100: - percent = 100 - # print ('进度条 %.2f%%' % percent, end = '\r') - sys.stdout.write("进度条 %.2f%%" % percent + "\r") - sys.stdout.flush() - - if url: - try: - log.debug( - """ - 正在下载 %s - 存储路径 %s - """ - % (url, file_path) - ) - if proxies: - # create the object, assign it to a variable - proxy = request.ProxyHandler(proxies) - # construct a new opener using your proxy settings - opener = request.build_opener(proxy) - # install the openen on the module-level - request.install_opener(opener) - - request.urlretrieve(url, file_path, progress_callfunc, data) - - log.debug( - """ - 下载完毕 %s - 文件路径 %s - """ - % (url, file_path) - ) - - call_func and call_func() - return 1 - except Exception as e: - log.error(e) - return 0 - else: - return 0 - - -def get_file_list(path, ignore=[]): - templist = path.split("*") - path = templist[0] - file_type = templist[1] if len(templist) >= 2 else "" - - # 递归遍历文件 - def get_file_list_(path, file_type, ignore, all_file=[]): - file_list = os.listdir(path) - - for file_name in file_list: - if file_name in ignore: - continue - - file_path = os.path.join(path, file_name) - if os.path.isdir(file_path): - get_file_list_(file_path, file_type, ignore, all_file) - else: - if not file_type or file_name.endswith(file_type): - all_file.append(file_path) - - return all_file - - return get_file_list_(path, file_type, ignore) if os.path.isdir(path) else [path] - - -def rename_file(old_name, new_name): - os.rename(old_name, new_name) - - -def del_file(path, ignore=()): - files = get_file_list(path, ignore) - for file in files: - try: - os.remove(file) - except Exception as e: - log.error( - """ - 删除出错: %s - Exception : %s - """ - % (file, str(e)) - ) - finally: - pass - - -def get_file_type(file_name): - """ - @summary: 取文件后缀名 - --------- - @param file_name: - --------- - @result: - """ - try: - return os.path.splitext(file_name)[1] - except Exception as e: - log.exception(e) - - -def get_file_path(file_path): - """ - @summary: 取文件路径 - --------- - @param file_path: /root/a.py - --------- - @result: /root - """ - try: - return os.path.split(file_path)[0] - except Exception as e: - log.exception(e) - - -############################################# - -# -# def exec_js(js_code): -# """ -# @summary: 执行js代码 -# --------- -# @param js_code: js代码 -# --------- -# @result: 返回执行结果 -# """ -# -# return execjs.eval(js_code) - - -# def compile_js(js_func): -# """ -# @summary: 编译js函数 -# --------- -# @param js_func:js函数 -# --------- -# @result: 返回函数对象 调用 fun('js_funName', param1,param2) -# """ -# -# ctx = execjs.compile(js_func) -# return ctx.call - - -############################################### - -############################################# - - -def date_to_timestamp(date, time_format="%Y-%m-%d %H:%M:%S"): - """ - @summary: - --------- - @param date:将"2011-09-28 10:00:00"时间格式转化为时间戳 - @param format:时间格式 - --------- - @result: 返回时间戳 - """ - - timestamp = time.mktime(time.strptime(date, time_format)) - return int(timestamp) - - -def timestamp_to_date(timestamp, time_format="%Y-%m-%d %H:%M:%S"): - """ - @summary: - --------- - @param timestamp: 将时间戳转化为日期 - @param format: 日期格式 - --------- - @result: 返回日期 - """ - if timestamp is None: - raise ValueError("timestamp is null") - - date = time.localtime(timestamp) - return time.strftime(time_format, date) - - -def get_current_timestamp(): - return int(time.time()) - - -def get_current_date(date_format="%Y-%m-%d %H:%M:%S"): - return datetime.datetime.now().strftime(date_format) - # return time.strftime(date_format, time.localtime(time.time())) - - -def get_date_number(year=None, month=None, day=None): - """ - @summary: 获取指定日期对应的日期数 - 默认当前周 - --------- - @param year: 2010 - @param month: 6 - @param day: 16 - --------- - @result: (年号,第几周,第几天) 如 (2010, 24, 3) - """ - if year and month and day: - return datetime.date(year, month, day).isocalendar() - elif not any([year, month, day]): - return datetime.datetime.now().isocalendar() - else: - assert year, "year 不能为空" - assert month, "month 不能为空" - assert day, "day 不能为空" - - -def get_between_date( - begin_date, end_date=None, date_format="%Y-%m-%d", **time_interval -): - """ - @summary: 获取一段时间间隔内的日期,默认为每一天 - --------- - @param begin_date: 开始日期 str 如 2018-10-01 - @param end_date: 默认为今日 - @param date_format: 日期格式,应与begin_date的日期格式相对应 - @param time_interval: 时间间隔 默认一天 支持 days、seconds、microseconds、milliseconds、minutes、hours、weeks - --------- - @result: list 值为字符串 - """ - - date_list = [] - - begin_date = datetime.datetime.strptime(begin_date, date_format) - end_date = ( - datetime.datetime.strptime(end_date, date_format) - if end_date - else datetime.datetime.strptime( - time.strftime(date_format, time.localtime(time.time())), date_format - ) - ) - time_interval = time_interval or dict(days=1) - - while begin_date <= end_date: - date_str = begin_date.strftime(date_format) - date_list.append(date_str) - - begin_date += datetime.timedelta(**time_interval) - - if end_date.strftime(date_format) not in date_list: - date_list.append(end_date.strftime(date_format)) - - return date_list - - -def get_between_months(begin_date, end_date=None): - """ - @summary: 获取一段时间间隔内的月份 - 需要满一整月 - --------- - @param begin_date: 开始时间 如 2018-01-01 - @param end_date: 默认当前时间 - --------- - @result: 列表 如 ['2018-01', '2018-02'] - """ - - def add_months(dt, months): - month = dt.month - 1 + months - year = dt.year + month // 12 - month = month % 12 + 1 - day = min(dt.day, calendar.monthrange(year, month)[1]) - return dt.replace(year=year, month=month, day=day) - - date_list = [] - begin_date = datetime.datetime.strptime(begin_date, "%Y-%m-%d") - end_date = ( - datetime.datetime.strptime(end_date, "%Y-%m-%d") - if end_date - else datetime.datetime.strptime( - time.strftime("%Y-%m-%d", time.localtime(time.time())), "%Y-%m-%d" - ) - ) - while begin_date <= end_date: - date_str = begin_date.strftime("%Y-%m") - date_list.append(date_str) - begin_date = add_months(begin_date, 1) - return date_list - - -def get_today_of_day(day_offset=0): - return str(datetime.date.today() + datetime.timedelta(days=day_offset)) - - -def get_days_of_month(year, month): - """ - 返回天数 - """ - - return calendar.monthrange(year, month)[1] - - -def get_firstday_of_month(date): - """'' - date format = "YYYY-MM-DD" - """ - - year, month, day = date.split("-") - year, month, day = int(year), int(month), int(day) - - days = "01" - if int(month) < 10: - month = "0" + str(int(month)) - arr = (year, month, days) - return "-".join("%s" % i for i in arr) - - -def get_lastday_of_month(date): - """'' - get the last day of month - date format = "YYYY-MM-DD" - """ - year, month, day = date.split("-") - year, month, day = int(year), int(month), int(day) - - days = calendar.monthrange(year, month)[1] - month = add_zero(month) - arr = (year, month, days) - return "-".join("%s" % i for i in arr) - - -def get_firstday_month(month_offset=0): - """'' - get the first day of month from today - month_offset is how many months - """ - (y, m, d) = get_year_month_and_days(month_offset) - d = "01" - arr = (y, m, d) - return "-".join("%s" % i for i in arr) - - -def get_lastday_month(month_offset=0): - """'' - get the last day of month from today - month_offset is how many months - """ - return "-".join("%s" % i for i in get_year_month_and_days(month_offset)) - - -def get_last_month(month_offset=0): - """'' - get the last day of month from today - month_offset is how many months - """ - return "-".join("%s" % i for i in get_year_month_and_days(month_offset)[:2]) - - -def get_year_month_and_days(month_offset=0): - """ - @summary: - --------- - @param month_offset: 月份偏移量 - --------- - @result: ('2019', '04', '30') - """ - - today = datetime.datetime.now() - year, month = today.year, today.month - - this_year = int(year) - this_month = int(month) - total_month = this_month + month_offset - if month_offset >= 0: - if total_month <= 12: - days = str(get_days_of_month(this_year, total_month)) - total_month = add_zero(total_month) - return (year, total_month, days) - else: - i = total_month // 12 - j = total_month % 12 - if j == 0: - i -= 1 - j = 12 - this_year += i - days = str(get_days_of_month(this_year, j)) - j = add_zero(j) - return (str(this_year), str(j), days) - else: - if (total_month > 0) and (total_month < 12): - days = str(get_days_of_month(this_year, total_month)) - total_month = add_zero(total_month) - return (year, total_month, days) - else: - i = total_month // 12 - j = total_month % 12 - if j == 0: - i -= 1 - j = 12 - this_year += i - days = str(get_days_of_month(this_year, j)) - j = add_zero(j) - return (str(this_year), str(j), days) - - -def add_zero(n): - return "%02d" % n - - -def get_month(month_offset=0): - """'' - 获取当前日期前后N月的日期 - if month_offset>0, 获取当前日期前N月的日期 - if month_offset<0, 获取当前日期后N月的日期 - date format = "YYYY-MM-DD" - """ - today = datetime.datetime.now() - day = add_zero(today.day) - - (y, m, d) = get_year_month_and_days(month_offset) - arr = (y, m, d) - if int(day) < int(d): - arr = (y, m, day) - return "-".join("%s" % i for i in arr) - - -@run_safe_model("format_date") -def format_date(date, old_format="", new_format="%Y-%m-%d %H:%M:%S"): - """ - @summary: 格式化日期格式 - --------- - @param date: 日期 eg:2017年4月17日 3时27分12秒 - @param old_format: 原来的日期格式 如 '%Y年%m月%d日 %H时%M分%S秒' - %y 两位数的年份表示(00-99) - %Y 四位数的年份表示(000-9999) - %m 月份(01-12) - %d 月内中的一天(0-31) - %H 24小时制小时数(0-23) - %I 12小时制小时数(01-12) - %M 分钟数(00-59) - %S 秒(00-59) - @param new_format: 输出的日期格式 - --------- - @result: 格式化后的日期,类型为字符串 如2017-4-17 3:27:12 - """ - if not date: - return "" - - if not old_format: - regex = "(\d+)" - numbers = get_info(date, regex, allow_repeat=True) - formats = ["%Y", "%m", "%d", "%H", "%M", "%S"] - old_format = date - for i, number in enumerate(numbers[:6]): - if i == 0 and len(number) == 2: # 年份可能是两位 用小%y - old_format = old_format.replace( - number, formats[i].lower(), 1 - ) # 替换一次 '2017年11月30日 11:49' 防止替换11月时,替换11小时 - else: - old_format = old_format.replace(number, formats[i], 1) # 替换一次 - - try: - date_obj = datetime.datetime.strptime(date, old_format) - if "T" in date and "Z" in date: - date_obj += datetime.timedelta(hours=8) - date_str = date_obj.strftime("%Y-%m-%d %H:%M:%S") - else: - date_str = datetime.datetime.strftime(date_obj, new_format) - - except Exception as e: - log.error("日期格式化出错,old_format = %s 不符合 %s 格式" % (old_format, date)) - date_str = date - - return date_str - - -@run_safe_model("format_time") -def format_time(release_time, date_format="%Y-%m-%d %H:%M:%S"): - if "年前" in release_time: - years = re.compile("(\d+)年前").findall(release_time) - years_ago = datetime.datetime.now() - datetime.timedelta( - days=int(years[0]) * 365 - ) - release_time = years_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "月前" in release_time: - months = re.compile("(\d+)月前").findall(release_time) - months_ago = datetime.datetime.now() - datetime.timedelta( - days=int(months[0]) * 30 - ) - release_time = months_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "周前" in release_time: - weeks = re.compile("(\d+)周前").findall(release_time) - weeks_ago = datetime.datetime.now() - datetime.timedelta(days=int(weeks[0]) * 7) - release_time = weeks_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "天前" in release_time: - ndays = re.compile("(\d+)天前").findall(release_time) - days_ago = datetime.datetime.now() - datetime.timedelta(days=int(ndays[0])) - release_time = days_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "小时前" in release_time: - nhours = re.compile("(\d+)小时前").findall(release_time) - hours_ago = datetime.datetime.now() - datetime.timedelta(hours=int(nhours[0])) - release_time = hours_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "分钟前" in release_time: - nminutes = re.compile("(\d+)分钟前").findall(release_time) - minutes_ago = datetime.datetime.now() - datetime.timedelta( - minutes=int(nminutes[0]) - ) - release_time = minutes_ago.strftime("%Y-%m-%d %H:%M:%S") - - elif "昨天" in release_time or "昨日" in release_time: - today = datetime.date.today() - yesterday = today - datetime.timedelta(days=1) - release_time = release_time.replace("昨天", str(yesterday)) - - elif "今天" in release_time: - release_time = release_time.replace("今天", get_current_date("%Y-%m-%d")) - - elif "刚刚" in release_time: - release_time = get_current_date() - - elif re.search("^\d\d:\d\d", release_time): - release_time = get_current_date("%Y-%m-%d") + " " + release_time - - elif not re.compile("\d{4}").findall(release_time): - month = re.compile("\d{1,2}").findall(release_time) - if month and int(month[0]) <= int(get_current_date("%m")): - release_time = get_current_date("%Y") + "-" + release_time - else: - release_time = str(int(get_current_date("%Y")) - 1) + "-" + release_time - - release_time = format_date(release_time, new_format=date_format) - - return release_time - - -def to_date(date_str, date_format="%Y-%m-%d %H:%M:%S"): - return datetime.datetime.strptime(date_str, date_format) - - -def get_before_date( - current_date, - days, - current_date_format="%Y-%m-%d %H:%M:%S", - return_date_format="%Y-%m-%d %H:%M:%S", -): - """ - @summary: 获取之前时间 - --------- - @param current_date: 当前时间 str类型 - @param days: 时间间隔 -1 表示前一天 1 表示后一天 - @param days: 返回的时间格式 - --------- - @result: 字符串 - """ - - current_date = to_date(current_date, current_date_format) - date_obj = current_date + datetime.timedelta(days=days) - return datetime.datetime.strftime(date_obj, return_date_format) - - -def delay_time(sleep_time=160): - """ - @summary: 睡眠 默认1分钟 - --------- - @param sleep_time: 以秒为单位 - --------- - @result: - """ - - time.sleep(sleep_time) - - -def format_seconds(seconds): - """ - @summary: 将秒转为时分秒 - --------- - @param seconds: - --------- - @result: 2天3小时2分49秒 - """ - - seconds = int(seconds + 0.5) # 向上取整 - - m, s = divmod(seconds, 60) - h, m = divmod(m, 60) - d, h = divmod(h, 24) - - times = "" - if d: - times += "{}天".format(d) - if h: - times += "{}小时".format(h) - if m: - times += "{}分".format(m) - if s: - times += "{}秒".format(s) - - return times - - -################################################ -def get_md5(*args): - """ - @summary: 获取唯一的32位md5 - --------- - @param *args: 参与联合去重的值 - --------- - @result: 7c8684bcbdfcea6697650aa53d7b1405 - """ - - m = hashlib.md5() - for arg in args: - m.update(str(arg).encode()) - - return m.hexdigest() - - -def get_sha1(*args): - """ - @summary: 获取唯一的40位值, 用于获取唯一的id - --------- - @param *args: 参与联合去重的值 - --------- - @result: ba4868b3f277c8e387b55d9e3d0be7c045cdd89e - """ - - sha1 = hashlib.sha1() - for arg in args: - sha1.update(str(arg).encode()) - return sha1.hexdigest() # 40位 - - -def get_base64(secret, message): - """ - @summary: 数字证书签名算法是:"HMAC-SHA256" - 参考:https://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages/ - --------- - @param secret: 秘钥 - @param message: 消息 - --------- - @result: 签名输出类型是:"base64" - """ - - import hashlib - import hmac - import base64 - - message = bytes(message, "utf-8") - secret = bytes(secret, "utf-8") - - signature = base64.b64encode( - hmac.new(secret, message, digestmod=hashlib.sha256).digest() - ).decode("utf8") - return signature - - -def get_uuid(key1="", key2=""): - """ - @summary: 计算uuid值 - 可用于将两个字符串组成唯一的值。如可将域名和新闻标题组成uuid,形成联合索引 - --------- - @param key1:str - @param key2:str - --------- - @result: - """ - - uuid_object = "" - - if not key1 and not key2: - uuid_object = uuid.uuid1() - else: - hash = md5(bytes(key1, "utf-8") + bytes(key2, "utf-8")).digest() - uuid_object = uuid.UUID(bytes=hash[:16], version=3) - - return str(uuid_object) - - -def get_hash(text): - return hash(text) - - -################################################## - - -def cut_string(text, length): - """ - @summary: 将文本按指定长度拆分 - --------- - @param text: 文本 - @param length: 拆分长度 - --------- - @result: 返回按指定长度拆分后形成的list - """ - - text_list = re.findall(".{%d}" % length, text, re.S) - leave_text = text[len(text_list) * length:] - if leave_text: - text_list.append(leave_text) - - return text_list - - -def get_random_string(length=1): - random_string = "".join(random.sample(string.ascii_letters + string.digits, length)) - return random_string - - -def get_random_password(length=8, special_characters=""): - """ - @summary: 创建随机密码 默认长度为8,包含大写字母、小写字母、数字 - --------- - @param length: 密码长度 默认8 - @param special_characters: 特殊字符 - --------- - @result: 指定长度的密码 - """ - - while True: - random_password = "".join( - random.sample( - string.ascii_letters + string.digits + special_characters, length - ) - ) - if ( - re.search("[0-9]", random_password) - and re.search("[A-Z]", random_password) - and re.search("[a-z]", random_password) - ): - if not special_characters: - break - elif set(random_password).intersection(special_characters): - break - - return random_password - - -def get_random_email(length=None, email_types: list = None, special_characters=""): - """ - 随机生成邮箱 - :param length: 邮箱长度 - :param email_types: 邮箱类型 - :param special_characters: 特殊字符 - :return: - """ - if not length: - length = random.randint(4, 12) - if not email_types: - email_types = [ - "qq.com", - "163.com", - "gmail.com", - "yahoo.com", - "hotmail.com", - "yeah.net", - "126.com", - "139.com", - "sohu.com", - ] - - email_body = get_random_password(length, special_characters) - email_type = random.choice(email_types) - - email = email_body + "@" + email_type - return email - - -################################# - - -def dumps_obj(obj): - return pickle.dumps(obj) - - -def loads_obj(obj_str): - return pickle.loads(obj_str) - - -def get_method(obj, name): - name = str(name) - try: - return getattr(obj, name) - except AttributeError: - log.error("Method %r not found in: %s" % (name, obj)) - return None - - -def witch_workspace(project_path): - """ - @summary: - --------- - @param project_path: - --------- - @result: - """ - - os.chdir(project_path) # 切换工作路经 - - -############### 数据库相关 ####################### -def format_sql_value(value): - if isinstance(value, str): - value = value.strip() - - elif isinstance(value, (list, dict)): - value = dumps_json(value, indent=None) - - elif isinstance(value, (datetime.date, datetime.time)): - value = str(value) - - elif isinstance(value, bool): - value = int(value) - - return value - - -def list2str(datas): - """ - 列表转字符串 - :param datas: [1, 2] - :return: (1, 2) - """ - data_str = str(tuple(datas)) - data_str = re.sub(",\)$", ")", data_str) - return data_str - - -def make_insert_sql( - table, data, auto_update=False, update_columns=(), insert_ignore=False -): - """ - @summary: 适用于mysql, oracle数据库时间需要to_date 处理(TODO) - --------- - @param table: - @param data: 表数据 json格式 - @param auto_update: 使用的是replace into, 为完全覆盖已存在的数据 - @param update_columns: 需要更新的列 默认全部,当指定值时,auto_update设置无效,当duplicate key冲突时更新指定的列 - @param insert_ignore: 数据存在忽略 - --------- - @result: - """ - - keys = ["`{}`".format(key) for key in data.keys()] - keys = list2str(keys).replace("'", "") - - values = [format_sql_value(value) for value in data.values()] - values = list2str(values) - - if update_columns: - if not isinstance(update_columns, (tuple, list)): - update_columns = [update_columns] - update_columns_ = ", ".join( - ["{key}=values({key})".format(key=key) for key in update_columns] - ) - sql = ( - "insert%s into {table} {keys} values {values} on duplicate key update %s" - % (" ignore" if insert_ignore else "", update_columns_) - ) - - elif auto_update: - sql = "replace into {table} {keys} values {values}" - else: - sql = "insert%s into {table} {keys} values {values}" % ( - " ignore" if insert_ignore else "" - ) - - sql = sql.format(table=table, keys=keys, values=values).replace("None", "null") - return sql - - -def make_update_sql(table, data, condition): - """ - @summary: 适用于mysql, oracle数据库时间需要to_date 处理(TODO) - --------- - @param table: - @param data: 表数据 json格式 - @param condition: where 条件 - --------- - @result: - """ - key_values = [] - - for key, value in data.items(): - value = format_sql_value(value) - if isinstance(value, str): - key_values.append("`{}`='{}'".format(key, value)) - elif value is None: - key_values.append("`{}`={}".format(key, "null")) - else: - key_values.append("`{}`={}".format(key, value)) - - key_values = ", ".join(key_values) - - sql = "update {table} set {key_values} where {condition}" - sql = sql.format(table=table, key_values=key_values, condition=condition) - return sql - - -def make_batch_sql( - table, datas, auto_update=False, update_columns=(), update_columns_value=() -): - """ - @summary: 生产批量的sql - --------- - @param table: - @param datas: 表数据 [{...}] - @param auto_update: 使用的是replace into, 为完全覆盖已存在的数据 - @param update_columns: 需要更新的列 默认全部,当指定值时,auto_update设置无效,当duplicate key冲突时更新指定的列 - @param update_columns_value: 需要更新的列的值 默认为datas里边对应的值, 注意 如果值为字符串类型 需要主动加单引号, 如 update_columns_value=("'test'",) - --------- - @result: - """ - if not datas: - return - - keys = list(datas[0].keys()) - values_placeholder = ["%s"] * len(keys) - - values = [] - for data in datas: - value = [] - for key in keys: - current_data = data.get(key) - current_data = format_sql_value(current_data) - - value.append(current_data) - - values.append(value) - - keys = ["`{}`".format(key) for key in keys] - keys = list2str(keys).replace("'", "") - - values_placeholder = list2str(values_placeholder).replace("'", "") - - if update_columns: - if not isinstance(update_columns, (tuple, list)): - update_columns = [update_columns] - if update_columns_value: - update_columns_ = ", ".join( - [ - "`{key}`={value}".format(key=key, value=value) - for key, value in zip(update_columns, update_columns_value) - ] - ) - else: - update_columns_ = ", ".join( - ["`{key}`=values(`{key}`)".format(key=key) for key in update_columns] - ) - sql = "insert into {table} {keys} values {values_placeholder} on duplicate key update {update_columns}".format( - table=table, - keys=keys, - values_placeholder=values_placeholder, - update_columns=update_columns_, - ) - elif auto_update: - sql = "replace into {table} {keys} values {values_placeholder}".format( - table=table, keys=keys, values_placeholder=values_placeholder - ) - else: - sql = "insert ignore into {table} {keys} values {values_placeholder}".format( - table=table, keys=keys, values_placeholder=values_placeholder - ) - - return sql, values - - -############### json相关 ####################### - - -def key2underline(key): - regex = "[A-Z]*" - capitals = re.findall(regex, key) - - if capitals: - for pos, capital in enumerate(capitals): - if not capital: - continue - if pos == 0: - if len(capital) > 1: - key = key.replace(capital, capital.lower() + "_", 1) - else: - key = key.replace(capital, capital.lower(), 1) - else: - if len(capital) > 1: - key = key.replace(capital, "_" + capital.lower() + "_", 1) - else: - key = key.replace(capital, "_" + capital.lower(), 1) - - return key.strip("_") - - -def key2hump(key): - """ - 下划线试变成首字母大写 - """ - return key.title().replace("_", "") - - -def format_json_key(json_data): - json_data_correct = {} - for key, value in json_data.items(): - key = key2underline(key) - json_data_correct[key] = value - - return json_data_correct - - -def quick_to_json(text): - """ - @summary: 可快速将浏览器上的header转为json格式 - --------- - @param text: - --------- - @result: - """ - - contents = text.split("\n") - json = {} - for content in contents: - if content == "\n": - continue - - content = content.strip() - regex = ["(:?.*?):(.*)", "(.*?):? +(.*)", "([^:]*)"] - - result = get_info(content, regex) - result = result[0] if isinstance(result[0], tuple) else result - try: - json[result[0]] = eval(result[1].strip()) - except: - json[result[0]] = result[1].strip() - - return json - - -############################## - - -def print_pretty(object): - pprint(object) - - -def print_params2json(url): - params_json = {} - params = url.split("?")[-1].split("&") - for param in params: - key_value = param.split("=", 1) - params_json[key_value[0]] = key_value[1] - - print(dumps_json(params_json)) - - -def print_cookie2json(cookie_str_or_list): - if isinstance(cookie_str_or_list, str): - cookie_json = {} - cookies = cookie_str_or_list.split("; ") - for cookie in cookies: - name, value = cookie.split("=") - cookie_json[name] = value - else: - cookie_json = get_cookies_from_selenium_cookie(cookie_str_or_list) - - print(dumps_json(cookie_json)) - - -############################### - - -def flatten(x): - """flatten(sequence) -> list - Returns a single, flat list which contains all elements retrieved - from the sequence and all recursively contained sub-sequences - (iterables). - Examples: - >>> [1, 2, [3,4], (5,6)] - [1, 2, [3, 4], (5, 6)] - >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, (8,9,10)]) - [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10] - >>> flatten(["foo", "bar"]) - ['foo', 'bar'] - >>> flatten(["foo", ["baz", 42], "bar"]) - ['foo', 'baz', 42, 'bar'] - """ - return list(iflatten(x)) - - -def iflatten(x): - """iflatten(sequence) -> iterator - Similar to ``.flatten()``, but returns iterator instead""" - for el in x: - if _is_listlike(el): - for el_ in flatten(el): - yield el_ - else: - yield el - - -def _is_listlike(x): - """ - >>> _is_listlike("foo") - False - >>> _is_listlike(5) - False - >>> _is_listlike(b"foo") - False - >>> _is_listlike([b"foo"]) - True - >>> _is_listlike((b"foo",)) - True - >>> _is_listlike({}) - True - >>> _is_listlike(set()) - True - >>> _is_listlike((x for x in range(3))) - True - >>> _is_listlike(six.moves.xrange(5)) - True - """ - return hasattr(x, "__iter__") and not isinstance(x, (six.text_type, bytes)) - - -################### - - -def re_def_supper_class(obj, supper_class): - """ - 重新定义父类 - @param obj: 类 如 class A: 则obj为A 或者 A的实例 a.__class__ - @param supper_class: 父类 - @return: - """ - obj.__bases__ = (supper_class,) - -################### - - -# def is_in_rate_limit(rate_limit, *key): -# """ -# 频率限制 -# :param rate_limit: 限制时间 单位秒 -# :param key: 限制频率的key -# :return: True / False -# """ -# msg_md5 = get_md5(*key) -# key = "rate_limit:{}".format(msg_md5) -# if get_redisdb().get(key): -# return True -# -# get_redisdb().set(key, time.time(), ex=rate_limit) -# return False - - -# def dingding_warning( -# message, -# rate_limit=3600, -# message_prefix=None, -# url=setting.DINGDING_WARNING_URL, -# user_phone=setting.DINGDING_WARNING_PHONE, -# ): -# if not url: -# log.info("未设置叮叮的地址,不支持报警") -# return -# -# if is_in_rate_limit(rate_limit, url, user_phone, message_prefix or message): -# log.info("报警时间间隔过短,此次报警忽略。 内容 {}".format(message)) -# return -# -# data = { -# "msgtype": "text", -# "text": {"content": message}, -# "at": {"atMobiles": [user_phone], "isAtAll": False}, -# } -# -# headers = {"Content-Type": "application/json"} -# -# response = requests.post(url, headers=headers, data=json.dumps(data).encode("utf8")) -# return response diff --git a/spiders/image_spider.py b/spiders/image_spider.py new file mode 100644 index 0000000..b94df04 --- /dev/null +++ b/spiders/image_spider.py @@ -0,0 +1,43 @@ +# -*- coding utf-8 -*-# +# ------------------------------------------------------------------ +# Name: ImageSpider +# Author: liangbaikai +# Date: 2021/1/11 +# Desc: there is a python file description +# ------------------------------------------------------------------ +# https://sme-1256044673.cos.ap-chengdu.myqcloud.com/suining/SUININGAE5BFB4AA624404F83491CAF53BE270E.JPG +from smart.item import Item +from smart.pipline import Piplines +from smart.response import Response +from smart.spider import Spider + + +class ImageItem(Item): + img = None + + +pip = Piplines() + + +@pip.pipline(2) +def imgpip(spider_ins, item): + print(f"imgpip {item.results}") + with open("1222223.jpg", 'wb') as fd: + fd.write(item.results.get('img')) + return item + + +class ImageSpider(Spider): + start_urls = [ + "https://sme-1256044673.cos.ap-chengdu.myqcloud.com/suining/SUININGAE5BFB4AA624404F83491CAF53BE270E.JPG" + ] + cutome_setting_dict = {**Spider.cutome_setting_dict, **{"piplines_instance": pip}} + + def parse(self, response: Response): + print(response.content) + item = ImageItem.get_item() + item.img = response.content + item.img = response.content + item.img = response.content + + yield item diff --git a/test/ruia_test.py b/test/ruia_test.py deleted file mode 100644 index 780bd05..0000000 --- a/test/ruia_test.py +++ /dev/null @@ -1,73 +0,0 @@ -# -*- coding utf-8 -*-# -# ------------------------------------------------------------------ -# Name: ruia_test -# Author: liangbaikai -# Date: 2020/12/31 -# Desc: there is a python file description -# ------------------------------------------------------------------ - -from ruia import AttrField, Item, Request, Spider, TextField -from ruia_ua import middleware - - -class ArchivesItem(Item): - """ - eg: http://www.ruanyifeng.com/blog/archives.html - """ - target_item = TextField(css_select='div#beta-inner li.module-list-item') - href = AttrField(css_select='li.module-list-item>a', attr='href') - - -class ArticleListItem(Item): - """ - eg: http://www.ruanyifeng.com/blog/essays/ - """ - target_item = TextField(css_select='div#alpha-inner li.module-list-item') - title = TextField(css_select='li.module-list-item>a') - href = AttrField(css_select='li.module-list-item>a', attr='href') - - -class BlogSpider(Spider): - """ - 针对博客源 http://www.ruanyifeng.com/blog/archives.html 的爬虫 - 这里为了模拟ua,引入了一个ruia的第三方扩展 - - ruia-ua: https://github.com/howie6879/ruia-ua - - pipenv install ruia-ua - - 此扩展会自动为每一次请求随机添加 User-Agent - """ - # 设置启动URL - start_urls = ['http://www.ruanyifeng.com/blog/archives.html'] - # 爬虫模拟请求的配置参数 - request_config = { - 'RETRIES': 3, - 'DELAY': 0, - 'TIMEOUT': 3 - } - # 请求信号量 - concurrency = 400 - blog_nums = 0 - - async def parse(self, res): - for page in range(1113): - print(page) - url = f'http://exercise.kingname.info/exercise_middleware_ip/{page}' - yield Request( - url, - callback=self.parse_item - ) - - async def parse_item(self, res): - print(res.html) - - - -class RuiaTestSpider(Spider): - request_config = { - 'RETRIES': 3, - 'DELAY': 0, - 'TIMEOUT': 3 - } - pass - -if __name__ == '__main__': - BlogSpider.start(middleware=middleware) diff --git a/test/uni_test.py b/test/uni_test.py index c2bc116..6e57b02 100644 --- a/test/uni_test.py +++ b/test/uni_test.py @@ -13,12 +13,12 @@ from smart.item import Item from smart.request import Request from smart.response import Response +from smart.tool import is_valid_url class TestItem(Item): # target_item = RegexField("\d+") age = RegexField("\d+", default="23222") - age2 = 33 def clean_age(self, value): @@ -26,7 +26,7 @@ def clean_age(self, value): class TestClassOne(object): - def test_response(self): + def test_1(self): request = Request("http://www.ocpe.com.cn/nengyuanjingji/zf/2020-08-29/3785.html") request.encoding = "utf-8" with open("test.html", "rb") as f: @@ -34,26 +34,12 @@ def test_response(self): # data='{"code":200,"msg":"success","data":{"SYS_NAME":"职业院校综合管理与内部质量诊断与改进平台","LOGO":"/dfs/2020/11/05/20201105143318-47f7d6e3ca5637b23468330cab0ec0f3.jpg","BACKGROUND":"/dfs/2020/10/19/20201019194723-cdec006aef30e1e8313ac25eb2b71e38.png"}}' response = Response(data, 200, request) res = response.xpath("//div[@class='xwt_a']//a/text()").getall() - print(res) print(response.links()) + print(res) def test_2(self): item = TestItem.get_item(html="sa11123s11s23sasasa01") print(item.results) - # for _item in item: - # print(_item) - - pass def test3(self): - x = """ - ss - """ - title = RegexField(re_select='"title": "(.*?)",', re_flags=re.DOTALL).extract(x) - print(title) - print('\033[1;31;40m 1111111是1111111') - - -if __name__ == '__main__': - pass - print(isinstance(None,bool)) + print(is_valid_url("http://www.baidu.com"))