templateData.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692
  1. import math
  2. import random
  3. from typing import List, Dict, Optional
  4. #首页数据
  5. class indexData:
  6. def __init__(self):
  7. # 通栏权重设置
  8. self.sectors_config = [
  9. {"name": "headLineSector", "total": 1, "weight": 10 ,"CNname":"网站头条" , "random": False},
  10. {"name": "bannerSector", "total": 1, "weight": 9 ,"CNname":"焦点图" , "random": False},
  11. {"name": "linkSector", "total": 1, "weight": 8 ,"CNname":"外链面板" , "random": False},
  12. {"name": "manyPictureSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合1" , "random": True},
  13. {"name": "commentSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合2" , "random": True},
  14. {"name": "listSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合3" , "random": True},
  15. {"name": "onlyImgSector", "total": 2, "weight": 7 ,"CNname":"带广告新闻组合" , "random": True},
  16. {"name": "adSector", "total": 4, "weight": 6 ,"CNname":"通栏广告" , "random": False}
  17. ]
  18. # 通栏数据
  19. self.sectors_data = {
  20. #广告通栏
  21. "adSector": {
  22. "sectorName": "adSector",
  23. "sectorCanvasHeight": 12, #占据画布高度
  24. "componentList": [
  25. {
  26. "component_type": 2,
  27. "component_style": 1,
  28. "sort": 1,
  29. "componentData": {}
  30. }
  31. ],
  32. "ad": {
  33. "width": 1200,
  34. "height": 90,
  35. "name": "",
  36. "price": 0,
  37. "introduce":"",
  38. "website_id": "",
  39. "thumb": "https://img.bjzxtw.org.cn/pre/image/png/20250530/1748588901281358.png",
  40. "typeid": 2,
  41. "ad_tag": ""
  42. }
  43. },
  44. #头条通栏
  45. "headLineSector": {
  46. "sectorName": "headLineSector",
  47. "sectorCanvasHeight": 17,
  48. "componentList": [
  49. {
  50. "component_type": 1,
  51. "component_style": 1,
  52. "sort": 1,
  53. "componentData": {
  54. "level": 1,
  55. "imgSize": 0,
  56. "textSize": 4,
  57. "child": {
  58. "id": "",
  59. "imgSize": "",
  60. "textSize": ""
  61. },
  62. "listType": [
  63. "id",
  64. "title",
  65. "imgurl",
  66. "author",
  67. "updated_at",
  68. "introduce",
  69. "islink",
  70. "linkurl",
  71. "copyfrom",
  72. "cat_arr_id",
  73. "catid",
  74. "pinyin"
  75. ],
  76. }
  77. }
  78. ]
  79. },
  80. #轮播图通栏
  81. "bannerSector": {
  82. "sectorName": "bannerSector",
  83. "sectorCanvasHeight": 44,
  84. "componentList": [
  85. {
  86. "component_type": 1,
  87. "component_style": 1,
  88. "sort": 1,
  89. "componentData": {
  90. "level": 2,
  91. "imgSize": 5,
  92. "textSize": 0,
  93. "child": {
  94. "id": "",
  95. "imgSize": "",
  96. "textSize": ""
  97. },
  98. "listType": [
  99. "id",
  100. "title",
  101. "imgurl",
  102. "author",
  103. "updated_at",
  104. "introduce",
  105. "islink",
  106. "linkurl",
  107. "copyfrom",
  108. "cat_arr_id",
  109. "catid",
  110. "pinyin"
  111. ],
  112. }
  113. },
  114. {
  115. "component_type": 1,
  116. "component_style": 1,
  117. "sort": 2,
  118. "componentData": {
  119. "level": 6,
  120. "imgSize": 0,
  121. "textSize": 10,
  122. "child": {
  123. "id": "",
  124. "imgSize": "",
  125. "textSize": ""
  126. },
  127. "listType": [
  128. "id",
  129. "title",
  130. "imgurl",
  131. "author",
  132. "updated_at",
  133. "introduce",
  134. "islink",
  135. "linkurl",
  136. "copyfrom",
  137. "cat_arr_id",
  138. "catid",
  139. "pinyin"
  140. ],
  141. }
  142. }
  143. ]
  144. },
  145. #新闻组合通栏1
  146. "manyPictureSector": {
  147. "sectorName": "manyPictureSector",
  148. "sectorCanvasHeight": 47,
  149. "componentList": [
  150. {
  151. "component_type": 1,
  152. "component_style": 1,
  153. "sort": 1,
  154. "componentData": {
  155. "category_id":"",
  156. "category_arr":"",
  157. "name":"请选择导航..",
  158. "level":"",
  159. "imgSize": 3,
  160. "textSize": 9,
  161. "child":{
  162. "id":"",
  163. "imgSize": "",
  164. "textSize": ""
  165. },
  166. "listType": [
  167. "id",
  168. "title",
  169. "imgurl",
  170. "author",
  171. "updated_at",
  172. "introduce",
  173. "islink",
  174. "linkurl",
  175. "copyfrom",
  176. "cat_arr_id",
  177. "catid",
  178. "pinyin"
  179. ],
  180. }
  181. },
  182. {
  183. "component_type": 1,
  184. "component_style": 1,
  185. "sort": 2,
  186. "componentData": {
  187. "category_id":"",
  188. "category_arr":"",
  189. "name":"请选择导航..",
  190. "level":"",
  191. "imgSize": 3,
  192. "textSize": 6,
  193. "child":{
  194. "id":"",
  195. "imgSize": "",
  196. "textSize": ""
  197. },
  198. "listType": [
  199. "id",
  200. "title",
  201. "imgurl",
  202. "author",
  203. "updated_at",
  204. "introduce",
  205. "islink",
  206. "linkurl",
  207. "copyfrom",
  208. "cat_arr_id",
  209. "catid",
  210. "pinyin"
  211. ],
  212. }
  213. }
  214. ]
  215. },
  216. #新闻组合通栏2
  217. "commentSector": {
  218. "sectorName": "commentSector",
  219. "sectorCanvasHeight": 47,
  220. "componentList": [
  221. {
  222. "component_type": 1,
  223. "component_style": 1,
  224. "sort": 1,
  225. "componentData": {
  226. "category_id":"",
  227. "category_arr":"",
  228. "name":"请选择导航..",
  229. "level":"",
  230. "imgSize": 2,
  231. "textSize": 12,
  232. "child":{
  233. "id":"",
  234. "imgSize": "",
  235. "textSize": ""
  236. },
  237. "listType": [
  238. "id",
  239. "title",
  240. "imgurl",
  241. "author",
  242. "updated_at",
  243. "introduce",
  244. "islink",
  245. "linkurl",
  246. "copyfrom",
  247. "cat_arr_id",
  248. "catid",
  249. "pinyin"
  250. ],
  251. }
  252. },
  253. {
  254. "component_type": 1,
  255. "component_style": 1,
  256. "sort": 2,
  257. "componentData": {
  258. "category_id":"",
  259. "category_arr":"",
  260. "name":"请选择导航..",
  261. "level":"",
  262. "imgSize": 1,
  263. "textSize": 3,
  264. "child":{
  265. "id":"",
  266. "imgSize": "",
  267. "textSize": ""
  268. },
  269. "listType": [
  270. "id",
  271. "title",
  272. "imgurl",
  273. "author",
  274. "updated_at",
  275. "introduce",
  276. "islink",
  277. "linkurl",
  278. "copyfrom",
  279. "cat_arr_id",
  280. "catid",
  281. "pinyin"
  282. ],
  283. }
  284. }
  285. ]
  286. },
  287. #新闻组合通栏3
  288. "listSector": {
  289. "sectorName": "listSector",
  290. "sectorCanvasHeight": 98,
  291. "componentList": [
  292. {
  293. "component_type": 1,
  294. "component_style": 1,
  295. "sort": 1,
  296. "componentData": {
  297. "category_id":"",
  298. "category_arr":"",
  299. "name":"请选择导航..",
  300. "level":"",
  301. "imgSize": 2,
  302. "textSize": 6,
  303. "child":{
  304. "id":"",
  305. "imgSize": "",
  306. "textSize": ""
  307. },
  308. "listType": [
  309. "id",
  310. "title",
  311. "imgurl",
  312. "author",
  313. "updated_at",
  314. "introduce",
  315. "islink",
  316. "linkurl",
  317. "copyfrom",
  318. "cat_arr_id",
  319. "catid",
  320. "pinyin"
  321. ],
  322. }
  323. },
  324. {
  325. "component_type": 1,
  326. "component_style": 1,
  327. "sort": 2,
  328. "componentData": {
  329. "category_id":"",
  330. "category_arr":"",
  331. "name":"请选择导航..",
  332. "level":"",
  333. "imgSize": 2,
  334. "textSize": 14,
  335. "child":{
  336. "id":"",
  337. "imgSize": "",
  338. "textSize": ""
  339. },
  340. "listType": [
  341. "id",
  342. "title",
  343. "imgurl",
  344. "author",
  345. "updated_at",
  346. "introduce",
  347. "islink",
  348. "linkurl",
  349. "copyfrom",
  350. "cat_arr_id",
  351. "catid",
  352. "pinyin"
  353. ],
  354. }
  355. },
  356. {
  357. "component_type": 1,
  358. "component_style": 1,
  359. "sort": 3,
  360. "componentData": {
  361. "category_id":"",
  362. "category_arr":"",
  363. "name":"请选择导航..",
  364. "level":"",
  365. "imgSize": 7,
  366. "textSize": 0,
  367. "child":{
  368. "id":"",
  369. "imgSize": "",
  370. "textSize": ""
  371. },
  372. "listType": [
  373. "id",
  374. "title",
  375. "imgurl",
  376. "author",
  377. "updated_at",
  378. "introduce",
  379. "islink",
  380. "linkurl",
  381. "copyfrom",
  382. "cat_arr_id",
  383. "catid",
  384. "pinyin"
  385. ],
  386. }
  387. }
  388. ]
  389. },
  390. #广告新闻混合通栏
  391. "onlyImgSector": {
  392. "sectorName": "onlyImgSector",
  393. "sectorCanvasHeight": 51,
  394. "componentList": [
  395. {
  396. "component_type": 1,
  397. "component_style": 1,
  398. "sort": 1,
  399. "componentData": {
  400. "category_id":"",
  401. "category_arr":"",
  402. "name":"请选择导航..",
  403. "level":"",
  404. "imgSize": 3,
  405. "textSize": 4,
  406. "child":{
  407. "id":"",
  408. "imgSize": "",
  409. "textSize": ""
  410. },
  411. "listType": [
  412. "id",
  413. "title",
  414. "imgurl",
  415. "author",
  416. "updated_at",
  417. "introduce",
  418. "islink",
  419. "linkurl",
  420. "copyfrom",
  421. "cat_arr_id",
  422. "catid",
  423. "pinyin"
  424. ],
  425. }
  426. },
  427. ],
  428. "ad": {
  429. "width": 450,
  430. "height": 290,
  431. "name": "",
  432. "price": 0,
  433. "introduce":"",
  434. "website_id": "",
  435. "thumb": "http://img.bjzxtw.org.cn/pre/image/png/20250609/174945725555092.png",
  436. "typeid": 2,
  437. "ad_tag": ""
  438. }
  439. },
  440. #外链通栏
  441. "linkSector": {
  442. "sectorName": "linkSector",
  443. "sectorCanvasHeight": 26,
  444. "componentList": [
  445. {
  446. "component_type": 3,
  447. "component_style": 1,
  448. "sort": 1,
  449. "componentData": {}
  450. }
  451. ]
  452. },
  453. }
  454. #模板数据
  455. self.sectors_webSiteData = {
  456. "base": {
  457. "websiteId": ""
  458. },
  459. "style": {
  460. "styleId": ""
  461. },
  462. "template": {
  463. "index": [],
  464. "class": [],
  465. "list": [],
  466. "article": [],
  467. "search": [],
  468. "aloneList": [],
  469. "aloneArticle": []
  470. },
  471. "ad": {
  472. "top":{
  473. "width": 830,
  474. "height": 110,
  475. "name": "",
  476. "price": 0,
  477. "introduce":"",
  478. "website_id": "",
  479. "thumb": "http://img.bjzxtw.org.cn/pre/image/png/20250527/1748332370111555.png",
  480. "typeid": 2,
  481. "ad_tag": ""
  482. },
  483. "index": [],
  484. "class": [],
  485. "list": [],
  486. "article": [],
  487. "search": [],
  488. "aloneList": [],
  489. "aloneArticle": []
  490. },
  491. "isSearch": True
  492. }
  493. #画布数据
  494. self.canvas_data = {
  495. "topAd":{
  496. "width": 830,
  497. "height": 110,
  498. "name": "",
  499. "price": "",
  500. "introduce": "",
  501. "website_id": "",
  502. "thumb": "http://192.168.1.234:19000/pre/image/jpeg/20251226/1766715237727691.jpg",
  503. "typeid": 2,
  504. "ad_tag": "",
  505. "ad_url": ""
  506. },
  507. "template":{
  508. "index":[],
  509. "class": [],
  510. "list": [],
  511. "article": [],
  512. "search": [],
  513. "aloneList": [],
  514. "aloneArticle": []
  515. }
  516. }
  517. #初始化通栏权重
  518. def reset_sectors_config(self):
  519. self.sectors_config = [
  520. {"name": "headLineSector", "total": 1, "weight": 10 ,"CNname":"网站头条"},
  521. {"name": "bannerSector", "total": 1, "weight": 9 ,"CNname":"焦点图"},
  522. {"name": "linkSector", "total": 1, "weight": 8 ,"CNname":"外链面板"},
  523. {"name": "manyPictureSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合1"},
  524. {"name": "commentSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合2"},
  525. {"name": "listSector", "total": 2, "weight": 7 ,"CNname":"新闻图文组合3"},
  526. {"name": "onlyImgSector", "total": 2, "weight": 7 ,"CNname":"带广告新闻组合"},
  527. {"name": "adSector", "total": 4, "weight": 6 ,"CNname":"通栏广告"}
  528. ]
  529. #创建一个templateData实例
  530. templateData = indexData()
  531. # 动态权重计算类
  532. class Sector:
  533. def __init__(self, name: str, total: int, weight: int, CNname: str):
  534. self.name = name # 名称
  535. self.total = total # 总数
  536. self.weight = weight # 权重
  537. self.remaining = total # 剩余数量 后面我们要对比 剩余数据/总数 来计算权重
  538. self.used_count = 0 # 记录已使用次数
  539. self.CNname = CNname # 中文名称
  540. """
  541. 思路:
  542. 1.按照权限的高低选择通栏
  543. 2.如果权限一样,随机选择
  544. 3.每选择两次新闻通栏后,adSector权重提升
  545. """
  546. class SectorScheduler:
  547. def __init__(self, sectors_config: List[Dict]):
  548. # 过滤掉total为0的sector(即不存在的sector)
  549. self.sectors = [
  550. Sector(sector['name'], sector['total'], sector['weight'], sector['CNname'])
  551. for sector in sectors_config
  552. if sector['total'] > 0
  553. ]
  554. self.history = [] # 记录选择历史
  555. self.consecutive_count = 0 # 连续选择相同权重sector的次数
  556. self.last_weight = None # 上一次选择的权重
  557. # 定义需要随机选择的sector组
  558. self.random_sector_group = {sector['name'] for sector in sectors_config if sector.get('random', False)}
  559. def calculate_dynamic_weight(self, sector: Sector) -> float:
  560. """计算动态权重 - 更复杂的权重计算"""
  561. if sector.remaining <= 0:
  562. return 0
  563. base_weight = sector.weight
  564. # 1. 基于剩余数量的权重调整
  565. usage_ratio = sector.remaining / sector.total
  566. quantity_factor = math.pow(usage_ratio, 0.4) # 剩余数量越多,权重相对越高
  567. # 2. 基于使用频率的权重调整(避免过度使用)
  568. if sector.used_count > 0:
  569. usage_frequency = 1.0 / (1 + math.log(1 + sector.used_count))
  570. else:
  571. usage_frequency = 1.0
  572. # 3. 对于adSector的特殊处理
  573. if sector.name == "adSector":
  574. # 检查是否需要插入adSector(每两个相同权重后)
  575. if self.consecutive_count >= 2 and self.last_weight == 7:
  576. ad_boost = 2.0 # 大幅提升adSector权重
  577. else:
  578. # 根据历史中adSector的出现频率调整
  579. recent_ad_count = self.history[-3:].count("adSector") if len(self.history) >= 3 else 0
  580. if recent_ad_count == 0:
  581. ad_boost = 1.5 # 一段时间没出现,适当提升
  582. else:
  583. ad_boost = 0.8 # 最近出现较多,适当降低
  584. else:
  585. ad_boost = 1.0
  586. # 4. 对于高权重sector的保护(确保它们优先被选择)
  587. if base_weight >= 8:
  588. priority_boost = 1.5
  589. else:
  590. priority_boost = 1.0
  591. dynamic_weight = base_weight * quantity_factor * usage_frequency * ad_boost * priority_boost
  592. return dynamic_weight
  593. def get_random_sector_from_group(self, target_weight: int) -> Optional[Sector]:
  594. """从随机组中随机选择一个指定权重的sector"""
  595. available_sectors = [
  596. sector for sector in self.sectors
  597. if sector.name in self.random_sector_group
  598. and sector.weight == target_weight
  599. and sector.remaining > 0
  600. ]
  601. if available_sectors:
  602. return random.choice(available_sectors)
  603. return None
  604. def select_next(self) -> Optional[str]:
  605. """选择下一个sector"""
  606. best_sector = None
  607. best_weight = -1
  608. current_weight = 7 # 随机选择组的权重
  609. # 首先检查是否需要强制插入adSector
  610. if (self.consecutive_count >= 2 and self.last_weight == current_weight and
  611. any(s.name == "adSector" and s.remaining > 0 for s in self.sectors)):
  612. ad_sector = next(s for s in self.sectors if s.name == "adSector" and s.remaining > 0)
  613. ad_sector.remaining -= 1
  614. ad_sector.used_count += 1
  615. # 更新历史记录
  616. self.history.append("adSector")
  617. self.consecutive_count = 0
  618. self.last_weight = ad_sector.weight
  619. return "adSector"
  620. # 正常选择逻辑
  621. for sector in self.sectors:
  622. if sector.remaining > 0:
  623. # 对于随机选择组的sector,使用统一的权重计算
  624. if sector.name in self.random_sector_group and sector.weight == current_weight:
  625. # 使用组内统一的权重值进行比较
  626. dynamic_weight = current_weight
  627. else:
  628. dynamic_weight = self.calculate_dynamic_weight(sector)
  629. if dynamic_weight > best_weight:
  630. best_weight = dynamic_weight
  631. best_sector = sector
  632. if best_sector:
  633. # 如果最佳sector属于随机选择组,则随机选择该组中的一个
  634. if best_sector.name in self.random_sector_group and best_sector.weight == current_weight:
  635. random_sector = self.get_random_sector_from_group(current_weight)
  636. if random_sector:
  637. best_sector = random_sector
  638. best_sector.remaining -= 1
  639. best_sector.used_count += 1
  640. # 更新连续选择计数
  641. if self.last_weight == best_sector.weight and best_sector.name != "adSector":
  642. self.consecutive_count += 1
  643. else:
  644. self.consecutive_count = 1
  645. self.last_weight = best_sector.weight
  646. self.history.append(best_sector.name)
  647. return best_sector.name
  648. return None
  649. def generate_sequence(self, count: int) -> List[str]:
  650. """生成指定长度的序列"""
  651. result = []
  652. for i in range(count):
  653. next_sector = self.select_next()
  654. if next_sector:
  655. result.append(next_sector)
  656. else:
  657. break # 没有可选的sector了
  658. return result