出于学习研究,对某账号的文章、视频分析一翻,尝试使用自动化方式看能否获取相应信息。

获取某号的文章有多重方法:

第一种是通过搜狗浏览器搜索账号(这种方式每天只能获取一篇文章,基本上没啥用。):

第二种方式需要自己注册一个订阅号,注册账号有限制:

1、同一个邮箱只能申请1个账号;
2、同一个手机号码可绑定5个账号;
3、同一身份证注册个人类型账号数量上限为1个;
4、同一企业、个体工商户、其他组织资料注册账号数量上限为2个;
5、同一政府、媒体类型可注册和认证50个账号;
6、同一境外主体注册账号数量上限为1个。
意思是一个人只能注册一个号体,申请一个媒体或政府可以有50个,研究学习的话,一个够用了。
注册流程就不说了,注册登录好后,点击“草稿箱”--->‘写新图文’--->点击顶部的‘超链接’ -->账号处点击‘选择其他账号’--->输入需要获取文章的账号名称,点击查询,这时候可以看到,会把该账号的所有文章都获取到:

比如我们查询“央视网”,文章会按照发布日期倒序显示,这就是我们要获取的数据。我们自动化操作也就模拟到这个地方,剩下的就分析接口就行:

通过请求分析,获取文章的请求为:https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin=35&count=5&fakeid=MTI0MDU3NDYwMQ==&type=9&query=&token=441171486&lang=zh_CN&f=json&ajax=1

接口返回数据很全,基本想要的数据都有了。

我们要做的事:

1.自动化模拟用户操作到当前动作,再获取请求连接,分析请求数据

2.直接调用这个接口获取数据,那么就要构造这个接口的请求参数,比如账号的id,cookie等,登录维护等。

比较两种方式,都有相应的好处,自动化只要登录后,输入账号名称即可,调用接口的话,不同的账号需要事先维护相应id等,直接调用接口更容易被封。

目前研究到的封禁规则为:每个号体可以调用接口60次,到了60次会被封禁1个小时,换IP也没用,1个小时候可以继续,多次被封禁后,封禁时间会变长,最长为24小时。

有人说,不调用接口,自动化后怎么获取这些请求?有两种方式:

1.安装mitmproxy,相比Charles、fiddler的优点在于,它可以命令行方式或脚本的方式进行mock

mitmproxy不仅可以像Charles那样抓包,还可以对请求数据进行二次开发,进入高度二次定制
大家可以先查看下官网的相关文档

mitmproxy 官网:https://www.mitmproxy.org/
mitmproxy很强大,但这个地方我们倒用不着。

1.seleniumwire

Selenium Wire 扩展了Selenium 的Python 绑定,让您可以访问浏览器发出的底层请求。您编写代码的方式与编写 Selenium 的方式相同,但您会获得额外的 API 来检查请求和响应并动态更改它们。
mitmproxy 官网:https://github.com/wkeeling/selenium-wire
  • Pure Python, user-friendly API
  • HTTP and HTTPS requests captured
  • Intercept requests and responses
  • Modify headers, parameters, body content on the fly
  • Capture websocket messages
  • HAR format supported
  • Proxy server support
 seleniumwire 就完全够我们使用了。
 知道怎么做后,我们梳理一下我们需要准备什么东西:
1.环境安装
(1)python环境(略)
(2)chromedriver驱动下载:http://chromedriver.storage.googleapis.com/index.html
(3)mysql环境(略)
2.数据库设计
(1)weichat_news:新闻主表
  1. CREATE TABLE `weichat_news` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `news_title` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章名称',
  4. `news_cover` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章封面图',
  5. `news_digest` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章简要',
  6. `news_content_link` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章内容连接',
  7. `account_id` int NOT NULL COMMENT '微信账号id',
  8. `weichat_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '微信账号名称',
  9. `news_create_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文章创建时间',
  10. `news_update_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文章更新时间',
  11. `update_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '更新时间',
  12. `insert_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '文章插入时间',
  13. `is_video` int NOT NULL DEFAULT '0' COMMENT '是否执行video扫描',
  14. `have_video` int NOT NULL DEFAULT '0' COMMENT '是否含有视频',
  15. `is_content` int NOT NULL DEFAULT '0' COMMENT '是否获取内容',
  16. `is_push` int DEFAULT '0',
  17. `is_delete` int NOT NULL DEFAULT '0',
  18. PRIMARY KEY (`id`),
  19. KEY `title` (`news_title`) USING BTREE,
  20. KEY `link` (`news_content_link`) USING BTREE,
  21. KEY `idx_account_id` (`account_id`) USING BTREE,
  22. KEY `idx_1` (`have_video`,`is_delete`,`is_push`) USING BTREE
  23. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章';
(2)weichat_account:需要获取的账号表
  1. CREATE TABLE `weichat_account` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号名称',
  4. `collection_id` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  5. `is_delete` int DEFAULT '0',
  6. PRIMARY KEY (`id`)
  7. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='账号';
(3)run_account:账号执行情况表
  1. CREATE TABLE `run_account` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `account_id` int NOT NULL COMMENT '账号id',
  4. `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号名称',
  5. `patch` int DEFAULT NULL,
  6. `run_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '执行时间',
  7. `is_delete` int DEFAULT '0',
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='执行情况';
(4)news_video:视频表
  1. CREATE TABLE `news_video` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `news_id` int NOT NULL COMMENT '文章id',
  4. `news_title` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '文章名称',
  5. `account_id` int NOT NULL COMMENT '公众id',
  6. `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号名称',
  7. `original_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '原视频url',
  8. `cover` varchar(300) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  9. `vid` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  10. `width` int NOT NULL COMMENT '视频宽度',
  11. `height` int NOT NULL COMMENT '视频高度',
  12. `video_quality_level` int NOT NULL COMMENT '视频级别',
  13. `video_quality_wording` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '清晰度',
  14. `qiniu_url` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '七牛转存url',
  15. `insert_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '插入时间',
  16. `is_delete` int DEFAULT '0',
  17. PRIMARY KEY (`id`)
  18. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='视频';
(5)news_content:新闻内容表
  1. CREATE TABLE `news_content` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `news_id` int NOT NULL,
  4. `news_title` varchar(100) CHARACTER SET utf8mb3 COLLATE utf8_general_ci DEFAULT NULL,
  5. `content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci,
  6. `insert_time` datetime DEFAULT NULL,
  7. `source` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  8. `is_delete` int DEFAULT '0',
  9. PRIMARY KEY (`id`),
  10. UNIQUE KEY `news_id` (`news_id`) USING BTREE
  11. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='文章详情表';
(6)account_token:用于存储账号后台的表
  1. CREATE TABLE `account_token` (
  2. `id` int NOT NULL AUTO_INCREMENT,
  3. `account` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '账号名称',
  4. `token` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '登录token',
  5. `update_time` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT 'token更新时间',
  6. `freq_control_time` datetime DEFAULT NULL,
  7. `is_delete` int DEFAULT '0',
  8. PRIMARY KEY (`id`)
  9. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='采集所用账号token';
3. 分模块设计
(1)通过自动化获取基础文章信息,比如:封面图,文章访问连接,文章时间等
(2)自动化打开文章连接获取该文章含有视频的地址,文章详情中的视频有两类:
  A. 文章详情普通视频,一般可以直接分析请求下载到(如下文章详情中比较中规中矩的视频):
        
  B.文章详情插入的TX视频,该格式是m3u8格式,需要下载ts文件合成mp4,如果是TX视频,自动化时候首先要去点击一下播放按钮,不然获取不到m3u8地址:
  

  两种视频处理方式不一样,我的处理思路是:所有文章都拿连接去自动化,用selenium的显示等待方式,如果发现视频按钮,就去点击一下,然后获取所有的请求数据后续分析。

(3)直接请求原文章地址,通过bs4、lxml解析内容
  A. 去除不需要的格式
  B. 图片转存七牛 (转存后替换原内容中图片)
  C. 提取原视频标签(后边视频下载转存七牛并转码后需要替换这里)
  D. 提取其他标签,比如新闻来源、编辑、记者等字段
(4) 既然账号有封禁,可以考虑加入过个账号,一旦账号被封禁,则退出当前账号,用新的账号登录,把登录二维码发送到钉钉供扫码
  A. 自动化时,账号被封禁了查询文章时没有数据,接口返回也是有提示“freq control”
     B. 当账号被封禁时,往表中插入一条记录,并更新封禁时间,取账号时,判断封禁时间大于1小时才取。
  C. 其实用那个账号取决于用手机扫描的是谁,浏览器有登录缓存的,账号没被封禁前,我们一直用的浏览器缓存,这样自动化时候,我们就不用每次去扫二维码了,后边会讲。
  D. 需要登录时,把二维码截图发到钉钉群,这里页面自动化等待时间可以设置长一点,比如10分钟。
  主要模块分类:
  

部分代码分享:

1 获取文章主方法:

  1. def get_news_main():
  2. """
  3. 获取文章
  4. :return:
  5. """
  6.  
  7. # 查询需要的号
  8. wei_account = read_sql.deal_mysql("get_account.sql")
  9. max_patch = read_sql.deal_mysql("get_max_patch.sql")[0][0]
  10.  
  11. if max_patch is None:
  12. max_patch = 1
  13. else:
  14. if len(wei_account) == 0:
  15. max_patch = max_patch + 1
  16. wei_account = read_sql.deal_mysql("get_account_all.sql")
  17. for i in wei_account:
  18. # now() > DATE_ADD(freq_control_time, INTERVAL 1 HOUR) // 这里获取没有被封禁的账号继续执行任务
  19. crawl_account_l = read_sql.deal_mysql("get_account_token.sql")
  20. if crawl_account_l:
  21. # 随机取一个用于爬取的账号
  22. crawl = random.randrange(len(crawl_account_l))
  23. # 所用来爬取账号的id
  24. crawl_account_id = crawl_account_l[crawl][0]
  25. # 所用来爬取账号的账号
  26. crawl_account = crawl_account_l[crawl][1]
  27. # # 所用来爬取账号的登录token
  28. crawl_token = crawl_account_l[crawl][2]
  29. # 待爬取的账号id
  30. account_id = i[0]
  31. # 待爬取的账号名称
  32. account_name = i[1]
  33. # 需要爬取的页数 //为1爬取两页,0开始
  34. page = 0
  35. # 自动化操作路径
  36. driver = news.get_news(crawl_token, account_name, page, crawl_account_id, crawl_account)
  37. try:
  38. time.sleep(1)
  39. # 获取文章数据
  40. news_data, freq = analyze_news.analyze_news(driver)
  41. # 数据插入数据库
  42. in_news.insert_news(account_id, account_name, news_data)
  43. # 获取了的账号存入run_account表
  44. in_run.insert_run_account(account_id, account_name, max_patch)
  45. # 如果账号被封禁则更新被禁时间,并退出当前账号
  46. if freq is True:
  47. ua.update_account_token_freq(crawl_account_id)
  48. # 被封禁的账号退出登录
  49. driver = lg.login_out(driver)
  50. except Exception as e:
  51. print(e)
  52. finally:
  53. driver.quit()
  54. else:
  55. print('账号封禁中,暂不能执行任务!!')
  56. break

(1)自动化获取方法

  1. # -*- coding = utf-8 -*-
  2. # ------------------------------
  3. # @time: 2022/5/5 17:11
  4. # @Author: drew_gg
  5. # @File: get_wei_chat_news.py
  6. # @Software: wei_chat_news
  7. # ------------------------------
  8.  
  9. import os
  10. import time
  11. import random
  12. import configparser
  13. from seleniumwire import webdriver
  14. from selenium.webdriver.common.by import By
  15. from selenium.webdriver.common.keys import Keys
  16. from get_news import get_login_token as gt
  17. from selenium.webdriver.support import expected_conditions as EC
  18. from selenium.webdriver.support.wait import WebDriverWait
  19.  
  20. pl = os.getcwd().split('wei_chat_news')
  21. # chromedriver地址
  22. driver_path = pl[0] + "wei_chat_news\\charomedriver\\chromedriver.exe"
  23. # 主配置文件地址
  24. config_path = pl[0] + "wei_chat_news\\common_config\\main_config.ini"
  25. config = configparser.ConfigParser()
  26. config.read(config_path)
  27.  
  28. def get_news(token, account_name, page_num, crawl_account_id, crawl_account):
  29. """
  30. 获取新闻
  31. :param token: 登录后token
  32. :param account_name: 获取文章的账号
  33. :param page_num: 获取文章的页数
  34. :param crawl_account_id: 用于获取的账号id
  35. :param crawl_account: 用于获取的账号
  36. :return:
  37. """
  38. # ************************************************** #
  39. # 由于微信账号的限制,一个账号只能爬取60页的数据,封禁1小时!!
  40. # ************************************************** #
  41.  
  42. # seleniumwire:浏览器请求分析插件
  43. # 浏览器缓存文件地址(谷歌浏览器)
  44. profile_directory = r'--user-data-dir=%s' % config.get('wei_chat', 'chromedriver_user_data')
  45.  
  46. # *******配置自动化参数*********#
  47. options = webdriver.ChromeOptions()
  48. # 加载浏览器缓存
  49. options.add_argument(profile_directory)
  50. # 避免代理跳转错误
  51. options.add_argument('--ignore-certificate-errors-spki-list')
  52. options.add_argument('--ignore-ssl-errors')
  53. options.add_argument('--ignore-ssl-errors')
  54. # 不打开浏览器运行
  55. # options.add_argument("headless")
  56. # *******配置自动化参数*********#
  57.  
  58. driver = webdriver.Chrome(executable_path=driver_path, options=options)
  59. # 最大化浏览器
  60. driver.maximize_window()
  61. wei_chat_url = config.get('wei_chat', 'wei_chat_url') + token
  62. # 微信账号地址
  63. driver.get(wei_chat_url)
  64. try:
  65. # 点击草稿箱 //如果找不到则认为是没有登录成功
  66. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, "//a[@title='草稿箱']"))).click()
  67. except Exception as e:
  68. # 调用扫码登录
  69. driver = gt.get_login_token(driver, wei_chat_url, crawl_account_id, crawl_account)
  70. print(e)
  71. try:
  72. # 点击草稿箱
  73. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, "//a[@title='草稿箱']"))).click()
  74. # 点击“新的创作”
  75. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.CLASS_NAME, "weui-desktop-card__icon-add"))).click()
  76. # 点击“写新图文”
  77. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.CLASS_NAME, "icon-svg-editor-appmsg"))).click()
  78. time.sleep(1)
  79. # 切换到新窗口
  80. driver.switch_to.window(driver.window_handles[1])
  81. # 点击“超链接”
  82. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.ID, "js_editor_insertlink"))).click()
  83. # 点击“其他账号”
  84. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, "//button[text()='选择其他账号']"))).click()
  85. # 输入“账号名称”
  86. driver.find_element(By.XPATH, value="//input[contains(@placeholder, '微信号')]").send_keys(account_name)
  87. # 回车查询
  88. driver.find_element(By.XPATH, value="//input[contains(@placeholder, '微信号')]").send_keys(Keys.ENTER)
  89. # 点击确认账号
  90. # 吃掉本次异常,不影响后续任务
  91. except Exception as e:
  92. print(e)
  93. try:
  94. e_v = "//*[@class='inner_link_account_nickname'][text()='%s']" % account_name
  95. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, e_v))).click()
  96. except Exception as e:
  97. ee = e
  98. print('没有该账号: ', account_name)
  99. return driver
  100. for i in range(page_num):
  101. time.sleep(random.randrange(1, 3))
  102. try:
  103. WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, "//a[text()='下一页']"))).click()
  104. try:
  105. # 暂无数据 //可能该账号没有文章,也可能被封了,直接返回!
  106. no_data_e = "//div[@class='weui-desktop-media-tips weui-desktop-tips'][text()='暂无数据']"
  107. nde = WebDriverWait(driver, 5, 0.2).until(EC.presence_of_element_located((By.XPATH, no_data_e)))
  108. if nde:
  109. return driver
  110. except Exception as e:
  111. ee = e
  112. except Exception as e:
  113. ee = e
  114. return driver
  115. return driver

(2)分析请求数据(包括解析文章请求、内容请求、视频请求、token等解析):

  1. # -*- coding = utf-8 -*-
  2. # ------------------------------
  3. # @time: 2022/5/5 17:56
  4. # @Author: drew_gg
  5. # @File: analyze_data_news.py
  6. # @Software: wei_chat_news
  7. # ------------------------------
  8.  
  9. import json
  10. import gzip
  11. from io import BytesIO
  12. from bs4 import BeautifulSoup
  13. from urllib.parse import urlparse, parse_qs
  14. from qn import common_qiniu as qn_url
  15.  
  16. def analyze_news(driver):
  17. """
  18. 分析账号内容
  19. :param driver:
  20. :return:
  21. """
  22. freq = False
  23. news_data = []
  24. for re in driver.requests:
  25. if "https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin=" in re.url:
  26. try:
  27. if "freq control" in str(re.response.body):
  28. freq = True
  29. else:
  30. buff = BytesIO(re.response.body)
  31. f = gzip.GzipFile(fileobj=buff)
  32. htmls = f.read().decode('utf-8')
  33. news_data.append(json.loads(htmls))
  34. except Exception as e:
  35. print(e)
  36. # return news_data
  37. if "https://mp.weixin.qq.com/cgi-bin/searchbiz?action=search_biz&begin=0&count=5" in re.url:
  38. if "freq control" in str(re.response.body):
  39. freq = True
  40. return news_data, freq
  41.  
  42. def analyze_video(driver, url=''):
  43. """
  44. 分析账号视频
  45. :param driver:
  46. :param url:
  47. :return:
  48. """
  49. news_data = []
  50. video_data = []
  51. video_cover = []
  52. vid = ''
  53. cover = ''
  54. tx_vid = ''
  55. tx_cover = ''
  56. for re in driver.requests:
  57. if url and url.split('#')[0].replace('http', 'https') in re.url:
  58. buff = BytesIO(re.response.body)
  59. f = gzip.GzipFile(fileobj=buff)
  60. htmls = f.read().decode('utf-8')
  61. ht = BeautifulSoup(htmls, features="lxml")
  62. video = ht.findAll('iframe', {'class': 'video_iframe'})
  63. if video:
  64. for v in video:
  65. if "vid" in v.attrs['data-src']:
  66. tx_vid = parse_qs(urlparse(v.attrs['data-src']).query)['vid'][0]
  67. if tx_vid + '.png' in re.url:
  68. tx_cover = qn_url.url_to_qiniu(re.url, 'png')
  69.  
  70. if "https://mp.weixin.qq.com/mp/videoplayer?action=get_mp_video_play_url" in re.url:
  71. pa = re.url.split('&')
  72. for i in pa:
  73. if 'VID' in i.upper():
  74. vid = i.split('=')[1]
  75. buff = BytesIO(re.response.body)
  76. f = gzip.GzipFile(fileobj=buff)
  77. htmls = f.read().decode('utf-8')
  78. html_dic = json.loads(htmls)
  79. html_dic['vid'] = vid
  80. news_data.append(html_dic)
  81. # print(json.loads(htmls))
  82. if "https://mp.weixin.qq.com/mp/videoplayer?action=get_mp_video_cover&vid=" in re.url:
  83. pa = re.url.split('&')
  84. for i in pa:
  85. if 'VID' in i.upper():
  86. vid = i.split('=')[1]
  87. news_cover = json.loads(re.response.body)['url']
  88. video_cover.append(vid + '@@@@' + news_cover)
  89. # 获取TX视频的视频
  90. if "https://vd6.l.qq.com/proxyhttp" in re.url:
  91. buf = BytesIO(re.response.body)
  92. f = gzip.GzipFile(fileobj=buf) # // gzip 文件498行做了修改,遇到异常中断执行,而不是抛出异常
  93. data = f.read().decode('utf-8')
  94. video_data.append(json.loads(data))
  95. video_dic = {}
  96. video_url = []
  97. # 视频路径 //json.loads(news_data[0]['vinfo'])['vl']['vi'][0]['ul']['ui'][3]['url']
  98. if video_data:
  99. for n1 in video_data:
  100. vi_dic = {}
  101. try:
  102. for n2 in (json.loads(n1['vinfo']))['vl']['vi']:
  103. vi_l = []
  104. vid = n2['vid']
  105. for n3 in n2['ul']['ui']:
  106. vi_l.append(n3['url'])
  107. vi_dic['url'] = vi_l
  108. except Exception as e:
  109. print("解析无vinfo!")
  110. print(e)
  111. video_url.append(vi_dic)
  112. video_dic[vid] = video_url
  113. if news_data:
  114. try:
  115. # 如果vid相同,把cover加入news_data
  116. for i, v1 in enumerate(news_data):
  117. if video_cover:
  118. for v2 in video_cover:
  119. if v1['vid'] == v2.split('@@@@')[0]:
  120. cover = v2.split('@@@@')[1]
  121. else:
  122. cover = ''
  123. news_data[i]['cover'] = cover
  124. except Exception as e:
  125. print(e)
  126. return news_data, video_dic, tx_cover
  127.  
  128. def analyze_token(driver):
  129. """
  130. 分析账号token
  131. :param driver:
  132. :return:
  133. """
  134. news_data = []
  135. for re in driver.requests:
  136. if "https://mp.weixin.qq.com/cgi-bin/home?t=home/index&lang=zh_CN&token=" in re.url:
  137. pa = re.url.split('=')[-1]
  138. news_data.append(pa)
  139. return news_data

2.获取视频主方法:

  1. def get_video_main():
  2. """
  3. 爬取文章视频
  4. :return:
  5. """
  6.  
  7. news_link = read_sql.deal_mysql("get_news_link.sql")
  8. if news_link:
  9. for i in news_link:
  10. link_url = i[4]
  11. print(" get video ing !")
  12. driver = video.get_video(link_url)
  13. try:
  14. time.sleep(1)
  15. # 获取文章数据
  16. news_data, video_dic, tx_cover = analyze_news.analyze_video(driver, link_url)
  17. # 如果有TX视频
  18. if video_dic:
  19. for k, y in video_dic.items():
  20. if k != '':
  21. for v in y:
  22. video_url = m3u8_mp4.m3u8_mp4_main(v['url'][0])
  23. vid = k
  24. if video_url:
  25. in_video.insert_video_tx(i, vid, video_url, tx_cover)
  26. un.update_news(i[0])
  27. else:
  28. print("无视频数据")
  29. # 数据插入数据库
  30. in_video.insert_video(i, news_data)
  31. if news_data:
  32. un.update_news(i[0])
  33. except Exception as e:
  34. print(e)
  35. finally:
  36. driver.quit()
  37. else:
  38. print("暂无数据处理!")

3.内容详情处理方法:

  1. def get_content_main():
  2. """
  3. 获取文章详情
  4. :return:
  5. """
  6. video_format = ' controlslist="nodownload"' \
  7. ' style="margin-right: auto;margin-left: auto;outline: 0px;' \
  8. 'box-sizing: border-box;' \
  9. 'vertical-align: inherit;' \
  10. 'display: block;clear: both;overflow-wrap: break-word !important;' \
  11. 'visibility: visible !important; max-width: 100%;' \
  12. '" controls="controls" src="'
  13. news_link_l = read_sql.deal_mysql("get_news_for_content.sql")
  14. if news_link_l:
  15. for i in news_link_l:
  16. news_id = i[0]
  17. news_title = i[1]
  18. news_link = i[2]
  19. have_video = i[3]
  20. print(datetime.datetime.now(), '*********** get content ing !')
  21. # 获取内容
  22. news_content, news_video, img_l, source = content.get_content(news_link)
  23. # # 更新视频表封面图
  24. # for c, k in cover.items():
  25. # in_video.update_video(vid=c, is_cover=1, cover=k)
  26. # data-src替换成src,把隐藏参数去掉
  27. news_content_all = news_content.replace('data-src', 'src').replace(' style="visibility: hidden;"', '')
  28. # url图片转存七牛并替换
  29. for im in img_l:
  30. time.sleep(0.1)
  31. new_img = qn_url.url_to_qiniu(im, 'png')
  32. news_content_all = news_content_all.replace(im, new_img)
  33. news_dic = [{'news_id': str(news_id)}]
  34. if have_video == 1:
  35. sql_result = read_sql.deal_more_mysql("get_video_for_content.sql", news_dic)
  36. if sql_result:
  37. for v1 in sql_result:
  38. video_html = '<video poster="' + v1[4] + '"' + video_format + v1[1] + '"></video>'
  39. for v2 in news_video:
  40. if v1[0] in str(v2):
  41. news_content_all = news_content_all.replace(str(v2).replace('data-src', 'src'), video_html)
  42. else:
  43. print('视频还未转存')
  44. continue
  45. # 插入内容
  46. try:
  47. in_content.insert_content(news_id, news_title, news_content_all, source)
  48. except Exception as e:
  49. print(e)
  50. # 更新weichat_news表的is_content字段
  51. un.update_news(news_id, 1)
  52.  
  53. else:
  54. print("暂无数据处理!")

4.遇到m3u8视频,处理方法:

  1. # -*- coding = utf-8 -*-
  2. # ------------------------------
  3. # @time: 2022/5/23 11:21
  4. # @Author: drew_gg
  5. # @File: get_m3u8.py
  6. # @Software: wei_chat_news
  7. # ------------------------------
  8.  
  9. import os
  10. import time
  11. import random
  12. import requests
  13. import subprocess
  14. import urllib3
  15. from qn import common_upload as qiniu
  16.  
  17. urllib3.disable_warnings()
  18.  
  19. project_path = os.path.abspath(os.path.dirname(__file__))
  20. video_path = os.path.abspath(os.path.join(os.path.dirname(__file__), 'video'))
  21.  
  22. user_agent_list = [
  23. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
  24. "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
  25. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
  26. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 (KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
  27. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
  28. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
  29. "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
  30. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
  31. "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
  32. "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
  33. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
  34. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
  35. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  36. "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  37. "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
  38. "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 (KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
  39. "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
  40. "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
  41. ]
  42.  
  43. def get_user_agent():
  44. header = {
  45. 'Accept': 'application/json, text/javascript, */*; q=0.01',
  46. 'Accept-Encoding': 'gzip, deflate',
  47. 'content-type': 'application/json',
  48. 'x-requested-with': 'XMLHttpRequest',
  49. 'Accept-Language': 'zh-CN,zh;q=0.8',
  50. 'User-Agent': random.choice(user_agent_list)}
  51. return header
  52.  
  53. def get_m3u8_file(url_m3u8, timeout=(10, 30), max_retry_time=3):
  54. """
  55. 获取m3u8信息
  56. :param url_m3u8: m3u8的视频地址
  57. :param timeout: 超时时间
  58. :param max_retry_time: 尝试次数
  59. :return:
  60. """
  61. # 获取主域名的完整路径
  62. host_path = get_path(url_m3u8)
  63. # 随机获取headers,防止被认为机器刷
  64. headers = get_user_agent()
  65. i = 1
  66. while i <= max_retry_time:
  67. try:
  68. res = requests.get(url=(url_m3u8.rstrip()).strip(), headers=headers, timeout=timeout, verify=False)
  69. if res.status_code != 200:
  70. return None
  71. # res.text 返回ts信息目录
  72. return host_path, res.text
  73. except Exception as e:
  74. print(e)
  75. i += 1
  76. return None
  77.  
  78. def deal_m3u8_file(host_path, content_m3u8):
  79. """
  80. 处理m3u8记录,获取单个的ts文件
  81. :param host_path: 主目录路径
  82. :param content_m3u8: m3u8记录
  83. :return:
  84. """
  85.  
  86. ts_file = {}
  87. content_m3u8 = content_m3u8.split('\n')
  88. for i, m in enumerate(content_m3u8):
  89. if "#EXTINF:" in m:
  90. ts_file[content_m3u8[i+1].split('?')[0]] = host_path + content_m3u8[i+1]
  91. if "#EXT-X-ENDLIST" in m:
  92. break
  93. # ts_file 返回ts字典{'ts名称':'ts连接'}
  94. return ts_file
  95.  
  96. def get_path(url):
  97. """
  98. 获取域名目录
  99. :param url: m3u8连接
  100. :return:
  101. """
  102. if url.rfind("/") != -1:
  103. return url[0:url.rfind("/")] + "/"
  104. else:
  105. return url[0:url.rfind("\\")] + "\\"
  106.  
  107. def download_ts_video(ts_url):
  108. """
  109. 下载ts文件
  110. :param ts_url:
  111. :return:
  112. """
  113. # 通过ts链接下载ts文件
  114. ts_name = str(int(time.time() * 1000000))
  115. save_ts_path = video_path + '\\' + ts_name + '\\'
  116. # 创建存放ts文件的目录
  117. cmd = "md %s" % save_ts_path
  118. os.popen(cmd)
  119. time.sleep(1)
  120. # 存放ts顺序与名称文件的txt文件名称
  121. ts_record_file = save_ts_path + ts_name + '.txt'
  122. # 构建copy /b 字符串 ts_str
  123. ts_str = ''
  124. # ts写入ts_record_file
  125. with open(ts_record_file, "w+") as f:
  126. for k, y in ts_url.items():
  127. try:
  128. response = requests.get(y, stream=True, verify=False)
  129. except Exception as e:
  130. print("异常请求:%s" % e.args)
  131. # 下载后ts文件的名称
  132. ts_path_file = save_ts_path + k
  133. # 下载ts文件
  134. with open(ts_path_file, "wb+") as file:
  135. for chunk in response.iter_content(chunk_size=1024):
  136. if chunk:
  137. file.write(chunk)
  138. f.write("file '%s'\n" % k)
  139. ts_str += k + '+'
  140. return ts_record_file, save_ts_path, ts_str.strip('+')
  141.  
  142. def ts_to_mp4(ts_txt, save_ts_path, ts_str):
  143. """
  144. 合并ts文件生成mp4文件
  145. :param ts_txt: ts记录表txt文件 // 用于ffmpeg合并所用
  146. :param save_ts_path: 存放ts文件路径
  147. :param ts_str: ts顺序串 // 1.ts + 2.ts + 3.ts + ……
  148. :return:
  149. """
  150. # 存放txt文件的名称
  151. txt = ts_txt.split(save_ts_path)[1]
  152. # 合并后存放视频的名称
  153. video_name = txt.split('.')[0] + '.mp4'
  154. # 生成copy /b 命令
  155. copy_cmd = "cd /d " + save_ts_path + "&copy /b " + ts_str + ' ' + video_name
  156. # 生成ffmpeg命令 //由于windows下python无法用管理员执行ffmpeg,只能舍弃这种合并方式
  157. # ffmpeg_cmd = "cd /d %s & ffmpeg -f concat -safe 0 -i %s -c copy %s" % (save_ts_path, txt, video_name)
  158. # print(copy_cmd)
  159. # print(ffmpeg_cmd)
  160. try:
  161. # 管道cmd模式执行cmd命令,可以收到命令执行结果
  162. subprocess.check_output(copy_cmd, shell=True, stderr=subprocess.PIPE)
  163. # ffmpeg的命令
  164. # subprocess.Popen(ffmpeg_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  165. except subprocess.CalledProcessError as err:
  166. raise AssertionError('合并文件失败!')
  167. return save_ts_path + video_name
  168.  
  169. def m3u8_mp4_main(url):
  170. """
  171. 获取m3u8信息下载并上传七牛并转码 // 暂时没有处理加密的ts文件
  172. :param url:
  173. :return:
  174. """
  175. # 获取m3u8信息 // ts文件信息和主目录路径
  176. video_download_url = ''
  177. try:
  178. host_path, text_m3u8 = get_m3u8_file(url)
  179. # 获取具体ts信息,ts_file字典{'ts名称':'ts连接'}
  180. ts_file = deal_m3u8_file(host_path, text_m3u8)
  181. # 下载ts文件,获取ts文件存放路径,copy /b命令需要的ts字符串、ffmpeg执行所需要的ts文件txt信息
  182. record_file_ts, ts_path_save, str_ts = download_ts_video(ts_file)
  183. # 获取合并后的文件
  184. video_file = ts_to_mp4(record_file_ts, ts_path_save, str_ts)
  185. # 视频上传七牛并转码
  186. video_download_url = qiniu.upload_qiniu(video_file)
  187. except Exception as e:
  188. print(e)
  189. finally:
  190. return video_download_url
  191.  
  192. if __name__ == '__main__':
  193.  
  194. m3u8_url = "https://apd-vlive.apdcdn.tc.qq.com/defaultts.tc.qq.com/svp_50001/D9WbuxKxEK_3ORIxMGPmLkb_Hf-9EYaHERGcUiwKAObEk-a" \
  195. "C0oWDQrKibK2oJu9pTk6cCYTLBJ7Ya_890oNNu3dwYOVwrsvU9m0A7bjXVSnVUWQ8BAb_6iyUrfHJdj6JLB9ORCEBpj6FY8Ew97akeKokNWBTMwN2Cmy" \
  196. "4xm72amqgqHo3zn0lSA/szg_6565_50001_0bc3waaaiaaagyamd24b4jrfdmgdasyaabca.f341003.ts.m3u8?ver=4"
  197. m3u8_mp4_main(m3u8_url)

常规视频分析如图(一般都是三个视频,超清、高清、流畅,至少有两个):

m3u8的视频:

解析vinfo后一般有3个m3u8地址:

  1. {'url':'https://apd-ab3bfda6e3c7f17903673bc29aa63be3.v.smtcdns.com/omts.tc.qq.com/AwfBnjY0sy7CusqUlUD8AyDJttC4uz-symHTokgF7fts/uwMROfz2r57EIaQXGdGnCmdeOm5ieD5vLVPWVxBgVS8DGd80/svp_50001/6JH7xBzZnXGE6Alc3apP8D4zuLTz0odtRlLJgtXefYU36iQhSBOLVoZp2S5N-SNIZ3x1oed-eIUVs_G-fLiqfq0LcwjmKOI353EmSEI_qltjWg0pnPvzVL-SjsSf5jD83PHEkaXv7JjRWPdNPPbt18hkmeDySTlIhMZkKdCn8X7P-Zz-5Iku_Q/szg_4751_50001_0bc3puaamaaa5eapln4k4nrfc7odaz6qabsa.f304110.ts.m3u8?ver=4','vt':2806,'dtc':0,'dt':2},
  2.  
  3. {'url':'https://apd-19bad465f8d944562bdc26c7556258de.v.smtcdns.com/omts.tc.qq.com/AwfBnjY0sy7CusqUlUD8AyDJttC4uz-symHTokgF7fts/uwMROfz2r57EIaQXGdGnCWdeOm4EnYbsz8uMYzCQBiqsbQpy/svp_50001/6JH7xBzZnXGE6Alc3apP8D4zuLTz0odtRlLJgtXefYU36iQhSBOLVoZp2S5N-SNIZ3x1oed-eIUVs_G-fLiqfq0LcwjmKOI353EmSEI_qltjWg0pnPvzVL-SjsSf5jD83PHEkaXv7JjRWPdNPPbt18hkmeDySTlIhMZkKdCn8X7P-Zz-5Iku_Q/szg_4751_50001_0bc3puaamaaa5eapln4k4nrfc7odaz6qabsa.f304110.ts.m3u8?ver=4','vt':2806,'dtc':0,'dt':2},
  4.  
  5. {'url':'https://apd-vlive.apdcdn.tc.qq.com/defaultts.tc.qq.com/svp_50001/6JH7xBzZnXGE6Alc3apP8D4zuLTz0odtRlLJgtXefYU36iQhSBOLVoZp2S5N-SNIZ3x1oed-eIUVs_G-fLiqfq0LcwjmKOI353EmSEI_qltjWg0pnPvzVL-SjsSf5jD83PHEkaXv7JjRWPdNPPbt18hkmeDySTlIhMZkKdCn8X7P-Zz-5Iku_Q/szg_4751_50001_0bc3puaamaaa5eapln4k4nrfc7odaz6qabsa.f304110.ts.m3u8?ver=4','vt':12800,'dtc':0,'dt':2}

目前看TX视频都是m3u8格式,好在ts文件都是没有加密的,所以也就没做解密处理。

强调几点:

1.视频的封面图和视频,都是有vid的,通过vid相同来处理视频的封面图,也是根据vid来替换视频,比如一篇文章中有多个视频。

2.账号执行表是为了处理异常执行情况,比如执行到一半,被封禁了,下次执行又得所有账号在来一遍,浪费时间,所以加了一个patch来标志,没执行完的pitch继续执行,执行完了下次执行时最大的patch加1,这样

就保障了这些任务。

视频记录:

钉钉处理登录情况:

再次说明:

以上仅供分享学习,请不要用于恶意爬虫!!

通过UI自动化方式获取文章、视频信息的更多相关文章

  1. php获取YouTube视频信息的方法

    YouTube的视频地址格式https://www.youtube.com/watch?v=[VIDEO_ID]例子:https://www.youtube.com/watch?v=psvkyf3Pz ...

  2. windows代码获取系统硬件信息的两种方式

    欢迎访问我的个人博客:xie-kang.com 原文地址 目前windows有两种方式获取系统硬件信息: 1)通过GetSystemFirmwareTable API获取SMBIOS信息,一段含丰富信 ...

  3. UI自动化(七)selenium简述

    1.什么是ui自动化模拟人用代码的方式去操作页面2.为什么要做ui自动化后期迭代的时候,老功能比较多,人工维护成本大这时候考虑引入ui自动化3.什么时候做ui自动化项目稳定,不在修改的某些老功能,为这 ...

  4. UI自动化selenium

    1.什么是UI自动化?模拟人用代码的方式去操作页面2.为什么要做UI自动化?后期迭代的时候,老功能比较多,人工维护成本较大,重复性工作较多,这个时候就考虑因为UI自动化3.什么时候做UI自动化?项目稳 ...

  5. Windows Phone 同步方式获取网络类型

    原文:Windows Phone 同步方式获取网络类型 在Windows Phone 开发中有时候需要获取设备当前连接网络的类型,是Wifi,还是2G,3G,或者4G,SDK中提供获取网络类型的API ...

  6. nodejs爬虫笔记(三)---爬取YouTube网站上的视频信息

    思路:通过笔记(二)中代理的设置,已经可以对YouTube的信息进行爬取了,这几天想着爬取网站下的视频信息.通过分析YouTube,发现可以从订阅号入手,先选择几个订阅号,然后爬取订阅号里面的视频分类 ...

  7. Docker容器获取宿主机信息

    最近在做产品授权的东西,开始宿主机为Window,程序获取机器硬件信息相对简单些,后来部署时发现各种各样的的环境问题,所有后来改用dokcer部署,docker方式获取宿主机信息时花了些时间,特此记录 ...

  8. Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比

    Android自动化测试中AccessibilityService获取控件信息(2)-三种方式对比   上一篇文章: Android自动化测试中AccessibilityService获取控件信息(1 ...

  9. UI自动化(selenium+python)之元素定位的三种等待方式

    前言 在UI自动化过程中,常遇到元素未找到,代码报错的情况.这种情况下,需要用等待wait. 在selenium中可以用到三种等待方式即sleep,implicitly_wait,WebDriverW ...

  10. Android简易实战教程--第四十七话《使用OKhttp回调方式获取网络信息》

    在之前的小案例中写过一篇使用HttpUrlConnection获取网络数据的例子.在OKhttp盛行的时代,当然要学会怎么使用它,本篇就对其基本使用做一个介绍,然后再使用它的接口回调的方式获取相同的数 ...

随机推荐

  1. vue中setTimeout之前 一定要 clearTimeout 否则将失效

    window.clearTimeout(this.singleClick) // 这句很重要,否则不起作用 this.singleClick = window.setTimeout(() => ...

  2. C++学习笔记之高级语法

    目录 高级语法 面向对象--类 对象的属性 运算符重载 拷贝构造函数 IO缓存 头文件的重复包含问题 深拷贝与浅拷贝 面向对象三大特性 高级语法 面向对象--类 C++使用struct.class来定 ...

  3. Review Book for GEE(Graduate Entrance Examination)

    English is made up of phrases and idioms, in the case of both written and spoken usage. When learnin ...

  4. 风控规则引擎(一):Java 动态脚本

    风控规则引擎(一):Java 动态脚本 日常场景 共享单车会根据微信分或者芝麻分来判断是否交押金 汽车租赁公司也会根据微信分或者芝麻分来判断是否交押金 在一些外卖 APP 都会提供根据你的信用等级来发 ...

  5. URL URI URN

    总结如下: 1.简写: URI (uniform resource identifier)统一资源标志符: URL(uniform resource location )统一资源定位符(或统一资源定位 ...

  6. JS(简单数据类型、数据类型转换)

    一. 数据类型简介 1.1 为什么需要数据类型 在计算机中,不同的数据所需占用的存储空间是不同的,为了便于把数据分成所需内存大小不同的数据,充分利用存储空间,于是定义了不同的数据类型.简单来说,数据类 ...

  7. 记录--7个Js async/await高级用法

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 7个Js async/await高级用法 JavaScript的异步编程已经从回调(Callback)演进到Promise,再到如今广泛使 ...

  8. Oracle 字符串拆分成一个一个字符

    SELECT (REGEXP_SUBSTR('LW112190', '[A-Z0-9]', 1, ROWNUM)) test FROM DUAL CONNECT BY ROWNUM <= LEN ...

  9. GIT版本控制学习博客

    GIT版本控制学习博客 环境部署 下载git版本控制即可. 用户配置 (1)设置用户及地址 git config --global user.name "Username" git ...

  10. 基于vivado中AXI的模型分析

    基于vivado中AXI的模型分析 1.底层代码 `timescale 1 ns / 1 ps module myip_v1_0_S00_AXI # ( // Users to add paramet ...