自研爬虫框架的经验总结(理论及方法)

news/发布时间2024/5/14 15:32:43

背景:

        由于业务需要,承接一部分的数据采集工作。目前市场内的一些通用框架不太适合。故而进行了自研。

        对比自研和目前成熟的框架,自研更灵活适配,可以自己组装核心方法;后者对于新场景的适配需要对框架本身有较高的理解。

        读了此文,你可以对自研爬虫框架有一个架构层面的认知。

        新手,可以开始模块化其中提到的模块类,将它们基类起来。如其中的log类、proxy代理类、redis、redis锁、db连接池、yaml配置读取,这些都可以提前封装。日常使用亦可。仓库可以使用gitlab或者github,gitee亦可。

        刚好有实际业务的话,完全可以自建一个。没有的话,就看看思路交流一下有可以。如有需要,可私信交流。

基本信息

语言:python3

框架:flask+蓝图,服务化。

中间件:redis、mysql、gevent、threadpool

有哪些功能:redis过滤、redis任务锁、db并发写入、任务并发触发执行、db连接池、较好的日志管理、引入质数的定时任务、支持一般统计。

其它:从框架而言,它是一个有较好规范和模块话的一个平台设计。新接入平台会非常方便。任务支持并发和触发。能满足绝大多数的采集场景。

爬虫框架核心结构

        爬虫目前几乎存在于各个行业,是大数据下衍生出来的一门技术。对于一些业务,通过爬虫采集的数据几乎占到平台自有数据的100%。

        爬虫的本质是数据整合。他面对的对象是目标数据对象各种各样的数据源。一般来讲,爬虫框架的采集模板需要包含以下几个部分。
 

公共方法:日志模块:logger即可。在我博文中有已封装好的logging类。可直接使用。            https://blog.csdn.net/i_likechard/article/details/133696767?spm=1001.2014.3001.5501db连接池:DBUtils==3.0proxy代理类:对接proxy平台即可;可以将其写入redis中,通过redis key的时间有效性来写入数据。redis操作:网上很多封装。redis锁:可以网上找,也可以通过chatgpt生成。主要通过redis锁来进行控制单一任务,再采集过程中又被触发的问题。
# -*- coding: UTF-8 -*-
"""
#time:
#author:
#desc: 
"""
from utils.redis_base_helper import rediscn_local
import timeclass RedisLock:def __init__(self, redis_client, key, expire=14400):self.redis_client = redis_clientself.key = keyself.expire = expireself.locked = Falsedef acquire(self):# 尝试获取锁result = self.redis_client.setnx(self.key, 1)if result == 1:# 获取锁成功,设置过期时间self.redis_client.expire(self.key, self.expire)self.locked = Truereturn Trueelse:# 获取锁失败return Falsedef release(self):if self.locked:# 释放锁self.redis_client.delete(self.key)self.locked = Falseclass LockManager:def __init__(self, redis_client):self.redis_client = redis_clientself.locks = {}def acquire_lock(self, key, expire=43200):if key not in self.locks:self.locks[key] = RedisLock(self.redis_client, key, expire)lock = self.locks[key]if lock.acquire():return lockelse:return Nonerds_lock_man = LockManager(rediscn_local.redis)config读写:使用yaml即可。建议存储一些默认值,不要将变化的值写入进去。维护部署比较麻烦。告警通知:钉钉、飞书都支持webhook的方式发群消息、也支持打电话、发短信。实在不行邮箱通知也可以,这种就比较好弄了。基础方法:(封装的request请求、数据拼接、md5、签名、jwt解析等等)基础信息:一些user-agent,city,type,等固定的信息。写死的一些基本配置。通过列表、字典存储到py文件中。代替config文件来管理。
city={'北京市': '110100', '天津市': '120100', '石家庄市': '130100', '唐山市': '130200', '秦皇岛市': '130300', '邯郸市': '130400', '邢台市': '130500', '保定市': '130600', '张家口市': '130700', '承德市': '130800', '沧州市': '130900', '廊坊市': '131000', '衡水市': '131100', '雄安新区': '131200', '太原市': '140100', '大同市': '140200', '阳泉市': '140300', '长治市': '140400', '晋城市': '140500', '朔州市': '140600', '晋中市': '140700', '运城市': '140800', '忻州市': '140900', '临汾市': '141000', '吕梁市': '141100', '呼和浩特市': '150100', '包头市': '150200', '乌海市': '150300', '赤峰市': '150400', '通辽市': '150500', '鄂尔多斯市': '150600', '呼伦贝尔市': '150700', '巴彦淖尔市': '150800', '乌兰察布市': '150900', '兴安盟': '152200', '锡林郭勒盟': '152500', '阿拉善盟': '152900', '沈阳市': '210100', '大连市': '210200', '鞍山市': '210300', '抚顺市': '210400', '本溪市': '210500', '丹东市': '210600', '锦州市': '210700', '营口市': '210800', '阜新市': '210900', '辽阳市': '211000', '盘锦市': '211100', '铁岭市': '211200', '朝阳市': '211300', '葫芦岛市': '211400', '长春市': '220100', '吉林市': '220200', '四平市': '220300', '辽源市': '220400', '通化市': '220500', '白山市': '220600', '松原市': '220700', '白城市': '220800', '延边朝鲜族自治州': '222400', '哈尔滨市': '230100', '齐齐哈尔市': '230200', '鸡西市': '230300', '鹤岗市': '230400', '双鸭山市': '230500', '大庆市': '230600', '伊春市': '230700', '佳木斯市': '230800', '七台河市': '230900', '牡丹江市': '231000', '黑河市': '231100', '绥化市': '231200', '大兴安岭地区': '232700', '上海市': '310100', '南京市': '320100', '无锡市': '320200', '徐州市': '320300', '常州市': '320400', '苏州市': '320500', '南通市': '320600', '连云港市': '320700', '淮安市': '320800', '盐城市': '320900', '扬州市': '321000', '镇江市': '321100', '泰州市': '321200', '宿迁市': '321300', '杭州市': '330100', '宁波市': '330200', '温州市': '330300', '嘉兴市': '330400', '湖州市': '330500', '绍兴市': '330600', '金华市': '330700', '衢州市': '330800', '舟山市': '330900', '台州市': '331000', '丽水市': '331100', '合肥市': '340100', '芜湖市': '340200', '蚌埠市': '340300', '淮南市': '340400', '马鞍山市': '340500', '淮北市': '340600', '铜陵市': '340700', '安庆市': '340800', '黄山市': '341000', '滁州市': '341100', '阜阳市': '341200', '宿州市': '341300', '六安市': '341500', '亳州市': '341600', '池州市': '341700', '宣城市': '341800', '福州市': '350100', '厦门市': '350200', '莆田市': '350300', '三明市': '350400', '泉州市': '350500', '漳州市': '350600', '南平市': '350700', '龙岩市': '350800', '宁德市': '350900', '南昌市': '360100', '景德镇市': '360200', '萍乡市': '360300', '九江市': '360400', '新余市': '360500', '鹰潭市': '360600', '赣州市': '360700', '吉安市': '360800', '宜春市': '360900', '抚州市': '361000', '上饶市': '361100', '济南市': '370100', '青岛市': '370200', '淄博市': '370300', '枣庄市': '370400', '东营市': '370500', '烟台市': '370600', '潍坊市': '370700', '济宁市': '370800', '泰安市': '370900', '威海市': '371000', '日照市': '371100', '莱芜市': '371200', '临沂市': '371300', '德州市': '371400', '聊城市': '371500', '滨州市': '371600', '菏泽市': '371700', '郑州市': '410100', '开封市': '410200', '洛阳市': '410300', '平顶山市': '410400', '安阳市': '410500', '鹤壁市': '410600', '新乡市': '410700', '焦作市': '410800', '濮阳市': '410900', '许昌市': '411000', '漯河市': '411100', '三门峡市': '411200', '南阳市': '411300', '商丘市': '411400', '信阳市': '411500', '周口市': '411600', '驻马店市': '411700', '省直辖县级行政区划': '469000', '武汉市': '420100', '黄石市': '420200', '十堰市': '420300', '宜昌市': '420500', '襄阳市': '420600', '鄂州市': '420700', '荆门市': '420800', '孝感市': '420900', '荆州市': '421000', '黄冈市': '421100', '咸宁市': '421200', '随州市': '421300', '恩施土家族苗族自治州': '422800', '长沙市': '430100', '株洲市': '430200', '湘潭市': '430300', '衡阳市': '430400', '邵阳市': '430500', '岳阳市': '430600', '常德市': '430700', '张家界市': '430800', '益阳市': '430900', '郴州市': '431000', '永州市': '431100', '怀化市': '431200', '娄底市': '431300', '湘西土家族苗族自治州': '433100', '广州市': '440100', '韶关市': '440200', '深圳市': '440300', '珠海市': '440400', '汕头市': '440500', '佛山市': '440600', '江门市': '440700', '湛江市': '440800', '茂名市': '440900', '肇庆市': '441200', '惠州市': '441300', '梅州市': '441400', '汕尾市': '441500', '河源市': '441600', '阳江市': '441700', '清远市': '441800', '东莞市': '441900', '中山市': '442000', '东沙群岛': '442101', '潮州市': '445100', '揭阳市': '445200', '云浮市': '445300', '南宁市': '450100', '柳州市': '450200', '桂林市': '450300', '梧州市': '450400', '北海市': '450500', '防城港市': '450600', '钦州市': '450700', '贵港市': '450800', '玉林市': '450900', '百色市': '451000', '贺州市': '451100', '河池市': '451200', '来宾市': '451300', '崇左市': '451400', '海口市': '460100', '三亚市': '460200', '三沙市': '460300', '重庆市': '500100', '成都市': '510100', '自贡市': '510300', '攀枝花市': '510400', '泸州市': '510500', '德阳市': '510600', '绵阳市': '510700', '广元市': '510800', '遂宁市': '510900', '内江市': '511000', '乐山市': '511100', '南充市': '511300', '眉山市': '511400', '宜宾市': '511500', '广安市': '511600', '达州市': '511700', '雅安市': '511800', '巴中市': '511900', '资阳市': '512000', '阿坝藏族羌族自治州': '513200', '甘孜藏族自治州': '513300', '凉山彝族自治州': '513400', '贵阳市': '520100', '六盘水市': '520200', '遵义市': '520300', '安顺市': '520400', '铜仁市': '522200', '黔西南布依族苗族自治州': '522300', '毕节市': '522400', '黔东南苗族侗族自治州': '522600', '黔南布依族苗族自治州': '522700', '昆明市': '530100', '曲靖市': '530300', '玉溪市': '530400', '保山市': '530500', '昭通市': '530600', '丽江市': '530700', '普洱市': '530800', '临沧市': '530900', '楚雄彝族自治州': '532300', '红河哈尼族彝族自治州': '532500', '文山壮族苗族自治州': '532600', '西双版纳傣族自治州': '532800', '大理白族自治州': '532900', '德宏傣族景颇族自治州': '533100', '怒江傈僳族自治州': '533300', '迪庆藏族自治州': '533400', '拉萨市': '540100', '昌都地区': '542100', '山南地区': '542200', '日喀则地区': '542300', '那曲地区': '542400', '阿里地区': '542500', '林芝地区': '542600', '西安市': '610100', '铜川市': '610200', '宝鸡市': '610300', '咸阳市': '610400', '渭南市': '610500', '延安市': '610600', '汉中市': '610700', '榆林市': '610800', '安康市': '610900', '商洛市': '611000', '兰州市': '620100', '嘉峪关市': '620200', '金昌市': '620300', '白银市': '620400', '天水市': '620500', '武威市': '620600', '张掖市': '620700', '平凉市': '620800', '酒泉市': '620900', '庆阳市': '621000', '定西市': '621100', '陇南市': '621200', '临夏回族自治州': '622900', '甘南藏族自治州': '623000', '西宁市': '630100', '海东市': '632100', '海北藏族自治州': '632200', '黄南藏族自治州': '632300', '海南藏族自治州': '632500', '果洛藏族自治州': '632600', '玉树藏族自治州': '632700', '海西蒙古族藏族自治州': '632800', '银川市': '640100', '石嘴山市': '640200', '吴忠市': '640300', '固原市': '640400', '中卫市': '640500', '乌鲁木齐市': '650100', '克拉玛依市': '650200', '吐鲁番地区': '652100', '哈密地区': '652200', '昌吉回族自治州': '652300', '博尔塔拉蒙古自治州': '652700', '巴音郭楞蒙古自治州': '652800', '阿克苏地区': '652900', '克孜勒苏柯尔克孜自治州': '653000', '喀什地区': '653100', '和田地区': '653200', '伊犁哈萨克自治州': '654000', '塔城地区': '654200', '阿勒泰地区': '654300', '自治区直辖县级行政区划': '659000', '台北': '710100', '高雄': '710200', '台南': '710300', '台中': '710400', '金门': '710500', '南投': '710600', '基隆': '710700', '新竹': '711300', '嘉义': '711900', '新北': '711100', '宜兰': '711200', '桃园': '711400', '苗栗': '711500', '彰化': '711700', '云林': '712100', '屏东': '712400', '台东': '712500', '花莲': '712600', '澎湖': '712700', '连江': '712800', '香港岛': '810100', '九龙': '810200', '新界': '810300', '澳门半岛': '820100'}
平台模块:内部包含一个base基类,使用语法糖定义采集前后事务,如可以集成执行时长统计、采集结束通知、redis锁控制等; 其它平台可在base类基础上进行拓展。一般只需要改动解析和采集部分即可。

base基类详解

初始化:init一些基本参数,如平台编号,延时、关联redis 键(这里主要是)等。读取配置文件(后期建议下掉,使用其它方式来动态配置。)、tokon队列解析(带伪装):这一步主要从处理数据-->清洗--》数据入库。伪装很重要,决定了采集的安全和可靠,如果是业务长期需要的数据源,这一步中的伪装需要好好设计。除了常见的延时、定时、接口请求header伪装。再升级伪装可以使用proxy、完整化请求链路、业务层逻辑伪装。其中逻辑层的伪装,需要结合业务场景来做,如果对方有安全部门,一般会以数量过滤到可疑对象,再逐步进行分析,过滤到异常用户。这里不同业务场景,风控的过滤不一样。就不延伸了。调研:提前通过工具制定采集步骤和流程。这一步也可以直接确定request 参数了。推荐postman或者apipost工具进行接口分析。另外还有爬虫工具库:spidertools.cn 等工具网站。过滤:过滤有些是前置、有些是后置的。前置的过滤一般是id或者关键词文本等唯一指标。后置过滤一般是更详细的一些规则。过滤的工具,推荐使用redis,速度快,效率高。带索引的db过滤亦可。统计:统计失败,成功,进行结果通知的步骤。除了邮件等方式,目前飞书和钉钉亦支持机器人或者电话、短信等方法。种类较多。

flask支撑

flask主要对采集平台做了两个层面的支撑。

#第一个是,
#定时任务的处理。使用flask-schedule; 定时的时间用的质数,如53。可以防止一段时间内重复。
class Config:JOBS = [# 半小时触发一次 1小时一次{'id': 'task1', 'func': "run:job1", 'trigger': 'interval', 'minutes': 53},{'id': 'task4', 'func': "run:job4", 'trigger': 'cron', 'day': '*', 'hour': '00', 'minute': '00', 'second': '10'},]SCHEDULER_TIMEZONE = 'Asia/Shanghai'  # 服务器设置时区SCHEDULER_EXECUTORS = {"default": {"type": "threadpool", "max_workers": 20}}SCHEDULER_JOB_DEFAULTS = {"coalesce": False, "max_instances": 3}SCHEDULER_API_ENABLED = True#第二个是接口触发采集。
这也是服务化的优点。可以在不动代码的情况下,完成一些操作,如控制触发采集全部或指定平台的采集。
@test.route("/ilikecharts/", methods=["GET"])
def do_job():if request.method == 'GET':data = json.loads(request.get_data(as_text=True))  # request.json.get("data", {})brand_name = str(data.get("brand", "demo"))#do somthing

基本运行逻辑

        差点忘了一个重要的part,程序是如何执行的。是如何把这些模块和组件结合在一起的。
       

运行flask任务后,程序会将所以plat类进行初始化。所以平台单个调试采集时,本质上就是实例化plat类,而后调用入口函数,这个入口函数一般会包含一个循环;后续对某个平台的采集其实就是调用它的这个类的入口函数。第一种接口方式,flask本身就是一个轻量级的后端,定义接口,调用指定脚本即可。如何调用后面讲第二种定时方式,使用flask-schedule通过cron和interval来实现定时刻,定间隔来执行。
from flask_apscheduler import APScheduler
两种方式触发后,程序通过,传入的平台编号,映射到响应的已实例的plat类的入口函数中。入口函数即是采集的入口。并将映射到的函数丢给已初始化的threadpool线程池,这样就实现了采集,并发采集。
MAPPING_PLATS = {"demo": demo.loop_fuc,"kk": m11.loop_fuc,
}
from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor(50)
for x in task_list:  #task_list=["demo","kk"]executor.submit(MAPPING_PLATS.get(x, demo.loop_by_page)) #这种方式也支持带参数调用,可以自研下。

 新平台如何接入       

        当触发机制(即运行逻辑)确定后,那如何接入新的平台呢?对于这个采集平台(自研爬虫框架)而言,前面的初始化、后续的过滤,上传、告警都在基类中实现了。变化的不同平台的解析方式。

        因为数据最后是需要落库db的。所以需要解析的字段,每个平台都是一样的。

        解析这一步各个平台不同,有通过api接口获取,有通过html解析。最后按页或者单条数据,将解析后的默认的字段(比如:id、name、age、desc等),按照list (列表每个元素存储的就是一条数据,一个list 装一页的数据)的方式,上传给 公共方法中的上传数据的fuction()即可。

#伪代码
def 入口函数:#初始化一些中间变量stop_flag= true 采集标志位error_time = 10 错误次数统计。for page in range(1, 100) #总页码可通过 其它方式获取。data_json_list = self.get_data(page)  #按页码或者按条来解析出来数据data_upload(data_json_list) #上传数据。def get_data(page):#这里需要分目标信息在page列表页还是详情页。#过滤页可以在这一步来完成。通过id或者其他唯一标识。 #如何实现过滤呢? 可以将已写入的数据,写入redis的set集合中。而后通过sismember判断id是否在其中,从而实现过滤。#解析出data_json_listreturn data_json_listdef data_upload(data): #这个是一个继承父类的方法,在基类中。内部会调用一个公共方法,用于上传数据-写入db等。return true,#一些统计标识

至此,一个大致的框架基本就介绍完毕了。至于一些细节,如果需要再交流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.bcls.cn/wvQR/3570.shtml

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

学习总结22

解题思路 简单模拟。 代码 #include <bits/stdc.h> using namespace std; long long g[2000000]; long long n; int main() {long long x,y,z,sum0,k0;scanf("%lld",&n);for(x1;x<n;x)scanf("%lld",&g[x]);for(x1;x<n;x){scanf(&qu…

物理备份的方式

完全备份恢复流程 停止数据库清理环境重演回滚&#xff0d;&#xff0d;> 恢复数据修改权限启动数据库 1.关闭数据库&#xff1a; [rootmysql-server ~]# systemctl stop mysqld [rootmysql-server ~]# rm -rf /var/lib/mysql/* //删除所有数据// [rootmysql-server ~]# …

unity Aaimation Rigging使用多个约束导致部分约束失去作用

在应用多个约束时&#xff0c;在Hierarchy的顺序可能会影响最终的效果。例如先应用了Aim Constraint&#xff0c;然后再应用Two Bone Constraint&#xff0c;可能会导致Two Bone Constraint受到Aim Constraint的影响而失效。因此&#xff0c;在使用多个约束时&#xff0c;应该仔…

【JVM】Java中SPI机制

打破双亲委派模型中提到SPI和JDBC相关内容&#xff0c;那么是如何打破双亲委派模型呢?本文进行一个讲解&#xff0c;在开始讲解之前&#xff0c;我们需要先了解Java中的SPI机制 是什么 SPI 全称Service Provider Interface&#xff0c;是 Java 提供的一套用来被第三方实现或…

Docker vs VM

关于应用程序的托管和开发&#xff0c;市场中的技术和产品琳琅满目。对比 Docker 和 VM&#xff0c;如何取舍&#xff1f;这主要由自身团队的因素决定&#xff0c;在选择 Docker 的情况下&#xff0c;你需要保证程序可在容器和虚拟机中运行。另外&#xff0c;成本和易用性也是重…

python毕设选题 - 大数据商城人流数据分析与可视化 - python 大数据分析

文章目录 0 前言课题背景分析方法与过程初步分析&#xff1a;总体流程&#xff1a;1.数据探索分析2.数据预处理3.构建模型 总结 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到…

ChatGPT 是什么

文章目录 一、ChatGPT 是什么二、ChatGPT的发明者三、ChatGPT的运作方式四、ChatGPT的技术五、ChatGPT的优势六、ChatGPT的局限性七、ChatGPT的应用八、ChatGPT的未来九、总结 一、ChatGPT 是什么 OpenAI的ChatGPT&#xff0c;即Chat Generative Pre-Trained Transformer&…

[算法沉淀记录] 排序算法 —— 归并排序

排序算法 —— 归并排序 算法介绍 归并排序是一种分治算法&#xff0c;由约翰冯诺伊曼在1945年发明。它的工作原理是将未排序的列表划分为n个子列表&#xff0c;每个子列表包含一个元素(包含一个元素的列表被认为是有序的)&#xff0c;然后重复合并子列表以生成新的有序子列表…

C#实用开发(14)--高清晰度字体和窗体分辨率问题。

新建winform程序是&#xff0c;又是会感觉到字体清晰度不够高。还有一种现象就是分辨率的问题&#xff0c;我们平常在自己的电脑开发是用125百分比的分辨率&#xff0c;实际部署的工控机是100&#xff0c;这就会导致分辨率不一致的问题。 可以通过新建应用程序清单&#xff0c;…

jetson nano——安装archiconda

目录 1.archiconda3我在这提供了下载链接&#xff0c;点解下面链接即可1.看好文件所在位置&#xff0c;如果装错了&#xff0c;那么环境变量的路径自己进行相应的修改。2.添加环境变量 2.可能部分伙伴输入一些激活&#xff0c;啥的命令激活不了&#xff0c;那么输入下面这些代码…

Redis实现滑动窗口限流

常见限流算法 固定窗口算法 在固定的时间窗口下进行计数&#xff0c;达到阈值就拒绝请求。固定窗口如果在窗口开始就打满阈值&#xff0c;窗口后半部分进入的请求都会拒绝。 滑动窗口算法 在固定窗口的基础上&#xff0c;窗口会随着时间向前推移&#xff0c;可以在时间内平滑控…

ClickHouse 指南(三)最佳实践 -- 稀疏主索引

在ClickHouse主索引的实用介绍 ClickHouse release 24.1, 2024-01-30 1、简介 在本指南中&#xff0c;我们将深入研究ClickHouse索引。我们将详细说明和讨论: ClickHouse中的索引与传统的关系数据库管理系统有何不同ClickHouse是如何构建和使用表的稀疏主索引的什么是在Clic…

潇洒郎:2024 IDEA、Pycharm获取最新激活码获取方式

IDEA获取最新激活码 https://idea.javatiku.cn/ 手机打开&#xff0c;看到验证码&#xff0c;30分钟有效&#xff0c;输入验证码 获取到最新激活码

【C++私房菜】面向对象中的多重继承以及菱形继承

文章目录 一、多重继承1、多重继承概念2、派生类构造函数和析构函数 二、菱形继承和虚继承2、虚继承后的构造函数和析构函数 三、has-a 与 is-a 一、多重继承 1、多重继承概念 **多重继承&#xff08;multiple inheritance&#xff09;**是指从多个直接基类中产生派生类的能力…

Jmeter基础(2) 目录介绍

目录 Jmeter目录介绍bin目录docsextrasliblicensesprintable_docs Jmeter目录介绍 在学习Jmeter之前&#xff0c;需要先对工具的目录有些了解&#xff0c;也会方便后续的学习 bin目录 examplesCSV目录中有CSV样例jmeter.batwindow 启动文件jmeter.shMac/linux的启动文件jmete…

基于Mapbox展示GDAL处理的3D行政区划展示实践

目录 前言 一、Gdal数据处理 1、数据展示 2、Java数据转换 二、Mapbox可视化 1、定义Mapbox地图 2、地图初始化 3、创建地图 三、界面优化 1、区域颜色设置 2、高度自适应和边界区分 3、中文标注 总结 前言 最近有遇到一个需求&#xff0c;用户想在地图上把行政区划…

十、线性代数二-线性相关

目录 1、线性相关的概念&#xff1a; 2、线性相关的代数表示&#xff1a; 3、线性相关的判断方法&#xff1a; 理解&#xff1a;线性相关指的是 向量组&#xff08;α1&#xff0c;α2&#xff0c;α3&#xff0c;...&#xff09;的 秩是 小于 k 的元数的&#xff0c;即齐次…

多任务爬虫(多线程和多进程)

在一台计算机中&#xff0c;我们可以同时打开多个软件&#xff0c;例如同时浏览网页、听音乐、打字等&#xff0c;这是再正常不过的事情。但仔细想想&#xff0c;为什么计算机可以同时运行这么多软件呢? 这就涉及计算机中的两个名词&#xff1a;多进程和多线程。 同样&#xf…

【Python笔记-设计模式】外观模式

一、说明 外观模式是一种结构型设计模式&#xff0c;能为程序库、框架或其他复杂类提供一个统一的接口。 (一) 解决问题 简化复杂系统的接口调用 (二) 使用场景 简化复杂系统&#xff1a;需要一个指向复杂子系统的直接接口&#xff0c; 且该接口的功能有限时重构复杂的代码…

15-36V降压充电光伏MPPT充电方案

1.MPPT原理--简介 MPPT&#xff0c;全称为Maximum Power Point Tracking&#xff0c;即最大功点跟踪&#xff0c;它是一种通过调节电气模块的工作状态&#xff0c;使光伏板能够输出更多电能的电气系统能够将太阳能电池板发出的直流电有效地贮存在蓄电池中&#xff0c;可有效地…
推荐文章