01. 动态数据的获取

1.1 动态数据与静态数据

  • 静态数据与动态数据的概念:
    • 静态数据:HTML中写死的数据,不管什么时候打开都是这样的,不会发生变化。
    • 动态数据:前端没有的数据,即需要向后端服务器请求的数据。
    • 动态数据产生的原因:客户端HTML文件中无法存储体量较大的数据,因此很多数据都是存储在服务器上的,只有当客户端需要的时候,才会进行请求并由服务器完全响应,以此减轻了服务器向客户端响应的IO压力以及客户端的存储压力。
  • requests模拟浏览器发送请求的方式只能获取静态数据。
  • 若想要获取动态数据,则需要自己监测浏览器的Network。捕捉发出去的请求,获取请求地址,然后通过这个地址就可以获取动态的数据。
  • 动态请求响应的数据一般是JSON格式的数据,JSON类似于Python中的列表嵌套字典或者字典嵌套列表。
  • 动态数据选用JSON作为数据格式的原因:

    • 几乎所有高级编程语言对JSON格式的数据都有较好的支持。
    • JSON是一种轻量级的数据,因此方便在客户端与服务器之间进行传递。

      1.2 动态数据捕捉

      1.2.1 浏览器捕捉动态数据

  • 现有王者荣耀英雄介绍的网站:https://pvp.qq.com/web201605/herolist.shtml

  • 用request获取这些英雄的名字,会发现总是少一些英雄。这是因为排在前面的英雄相较于后面而言,是最近才出的一些新英雄,王者荣耀官方还没有将这部分内容固定写到其HTML页面中,因此需要向服务器动态请求。
  • 通过F12 >> Network >> Fetch/XHR即可监控到所有动态数据,若没有监控到则打开Fetch/XHR后刷新一下页面即可。

image.png

  • 在Fetch/XHR中,我们可以双击下载与英雄信息相关的herolist.json文件,打开可以发现其中存储的是所有英雄的数据,包括新英雄在内。

image.png

1.2.2 Python捕捉动态数据。

  • Python捕捉动态数据常用的有两种方式,第一种方式较为愚钝,就是手动打开Fetch/XHR,然后单击需要的动态数据文件,在Headers中的General中即可获取该动态数据文件的URL。

image.png

  • 然后用requests模块请求该地址即可。 ```python import requests

resp = requests.get(url=”https://pvp.qq.com/web201605/js/herolist.json“) if resp.status_code == 200: print(resp.json()) # json()函数可以获取JSON格式的数据。 else: print(f”数据请求失败,状态码为:{resp.status_code}”)

  1. - 第二种方式,就是使用selenium自动化测试工具捕捉动态数据。
  2. - 示例:从[https://101.qq.com/#/hero](https://101.qq.com/#/hero)中获取英雄名称、英雄职业、技能名称。
  3. - 注:英雄数据列表和英雄详情页链接都是动态数据。
  4. ```python
  5. import requests
  6. # 这个地址也是个动态数据,即Fetch/XHR中的hero_list.js?ts=2764419地址。
  7. resp = requests.get(url="https://game.gtimg.cn/images/lol/act/img/js/heroList/hero_list.js?ts=2764419")
  8. if resp.status_code == 200:
  9. hero_list = resp.json()
  10. heroes = hero_list.get("hero") # 所有的英雄信息被封装成一个列表,是字典hero_list中键hero的值。
  11. for hero in heroes:
  12. hero_id = hero.get("heroId")
  13. hero_name = hero.get("name")
  14. hero_roles = "、".join(hero.get("roles"))
  15. # 动态地址,在英雄列表页面点击进入英雄的详情页,然后在Fetch/XHR中找到{hero_id}.js?ts=2764423文件的地址。
  16. hero_detail_url = f"https://game.gtimg.cn/images/lol/act/img/js/hero/{hero_id}.js?ts=2764423"
  17. resp_hero_detail = requests.get(url=hero_detail_url)
  18. if resp_hero_detail.status_code == 200:
  19. detail_data = resp_hero_detail.json()
  20. skills = detail_data.get("spells") # 英雄技能是一个列表,是字典detail_data中键spells的值。
  21. hero_skills = "、".join([skill.get("name") for skill in skills]) # 将所有技能名称提取出来,然后用“、”连接成字符串。
  22. print(hero_name, hero_roles, hero_skills)
  23. else:
  24. print(f"英雄详情页数据请求失败,状态码为:{resp.status_code}")
  25. else:
  26. print(f"数据请求失败,状态码为:{resp.status_code}")

02. selenium自动化测试

2.1 selenium概述与安装

2.1.1 selenium介绍

  • selenium是一款自动化测试工具,被称为爬虫的万金油。
  • selenium会自动调动浏览器,并展示页面信息。只要是浏览器上出现的数据,selenium都可以请求到。
    • 缺点:网页上隐藏的数据无法用selenium获取。
    • 所谓隐藏的数据,以英雄联盟狂战士的英雄详情页:https://101.qq.com/#/hero-detail?heroid=2&datatype=5v5为例,其技能描述只有当鼠标悬停在其技能图标上时才会显示,其他时候是不显示的。对于这种只有在特定场景下才显示的数据就称之为隐藏的数据。

image.png

  • 总结:

    • requests可以获取网页的HTML源码,因此只要是HTML源码中存在的数据,都可以用requests爬取;动态数据HTML源码中不存在,因此无法爬取。
    • selenium可以获取element中的数据,selenium会打开浏览器渲染页面,因此只要是页面中存在的数据,都可以用selenium获取,包括动态数据。但无法获取隐藏的数据,哪怕隐藏的数据存在于HTML源码中。
    • 因此可以说requests和selenium是两个互补的模块。

      2.1.2 selenium安装

  • selenium也是一款第三方工具库,也需要用pip工具进行安装。

    1. pip install selenium

    2.1.3 ChromeDriver驱动安装

  • 安装完第三方工具库selenium直接使用会报错:Message: 'chromedriver' executable needs to be in PATH.,报错原因是谷歌浏览器驱动没有被正常安装。

  • 第一步:查看Chrome谷歌浏览器版本。
    • 点击浏览器右上角的菜单按钮,再点击设置。

image.png

  • 接着点击左侧导航栏中的“关于 Chrome”,在右侧即可看到当前谷歌浏览器的版本。

image.png

image.png

  • 第五步:将下载好的压缩文件进行解压,得到chromedriver.exe驱动文件,然后右键谷歌浏览器图标 >> 打开文件所在的位置,将解压得到的chromedriver.exe驱动文件移动到打开的目录中。

image.png

  • 第六步:将打开的谷歌浏览器目录地址添加到PATH中。
  • 补充(第二、三步无法正常访问的解决方案):
    • 上述下载方式是谷歌官方提供的下载方式,其中的连接可能因为一些网络问题无法正常访问。
    • 替代方式:完成步骤一后,记录前三个数(即大版本号),然后去镜像地址:https://registry.npmmirror.com/binary.html?path=chromedriver/,根据大版本号寻找对应的资源。(即资源的最后第四位小版本号可以与浏览器实际的版本不同,但前三位要相同)
  • 安装完成后,重启一下PyCharm,即可正常运行selenium程序了。

    2.2 selenium的常用操作

    2.2.1 Chrome()启动谷歌浏览器

  • 第三方库selenium中有一个webdriver模块,这个模块下的Chrome()函数可以创建一个谷歌浏览器对象,并在当前操作系统中打开谷歌浏览器。 ```python from selenium import webdriver

browser = webdriver.Chrome()

  1. <a name="pVjnp"></a>
  2. #### 2.2.2 get()访问指定网站
  3. - `浏览器对象.get(网址字符串)`可以让浏览器在当前窗口中访问指定的网址。
  4. ```python
  5. from selenium import webdriver
  6. browser = webdriver.Chrome()
  7. browser.get("https://www.baidu.com/") # 访问百度

2.2.3 find_elements()查找浏览器上的标签元素

  • 浏览器对象.find_elements(by=By.ID, value=值)函数可以查询符合条件的所有标签元素。
    • 参数by:用于指定查找的参数,常用的参数如下:
      • ID:根据HTML标签的id属性来获取元素。
      • CLASS_NAME:根据HTML标签的类名来获取元素。
      • CSS_SELECTOR:根据CSS选择器来获取元素。
      • XPATH:更具XPath来获取元素。
      • 注意:上述所有的值都是selenium.webdriver.common.by.By中的对象。
    • 参数value:参数by指定的查找参数对应的值。
  • browser.find_element()
    • browser.find_elements()函数查找的是符合条件的所有标签,是一个列表;
    • browser.find_element()函数是查找符合条件的所有标签中的第一个,即browser.find_element()等价于browser.find_elements()[0]
  • 示例:与2.2.4一起演示。

    2.2.4 click()鼠标点击

  • 一般会先用browser.find_element()函数选出标签对象,然后再用标签对象.click()函数执行鼠标点击事件。

  • 示例:访问英雄联盟官网:https://lol.qq.com/main.shtml,然后点击网站顶部导航栏中的游戏资料。 ```python from selenium import webdriver from selenium.webdriver.common.by import By import time

调用浏览器访问英雄联盟官网

browser = webdriver.Chrome() browser.get(“https://lol.qq.com/main.shtml“)

查找游戏资料的链接标签,并进行点击

time.sleep(3) # 点击前先短暂休眠,以防服务器误认为是攻击行为 game_info = browser.find_element( by=By.CSS_SELECTOR, value=”#J_headNav > li > a” ) game_info.click()

  1. <a name="NaErp"></a>
  2. #### 2.2.5 ActionChains鼠标工具执行点击
  3. - 在selenium.webdriver中有一个ActionChains鼠标工具,封装了一系列的鼠标操作。
  4. ```python
  5. from selenium.webdriver import ActionChains
  • 实例化鼠标对象:鼠标对象 = ActionChains(浏览器对象)
  • 让鼠标点击目标标签:鼠标对象.click(标签对象).perform()
    • click(标签对象):鼠标点击事件,点击指定的标签。(事件不会立刻点击)
    • perform():执行事件,调用perform()函数后事件会立刻执行。
  • 示例:用ActionChains点击游戏资料。 ```python from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By import time

调用浏览器访问英雄联盟官网

browser = webdriver.Chrome() browser.get(“https://lol.qq.com/main.shtml“)

查找游戏资料的链接标签

time.sleep(3) game_info = browser.find_element(by=By.CSS_SELECTOR, value=”#J_headNav > li > a”)

点击链接标签

action = ActionChains(browser) action.click(game_info).perform()

  1. - ActionChains使用说明:[https://www.cnblogs.com/lxbmaomao/p/10389786.html](https://www.cnblogs.com/lxbmaomao/p/10389786.html)
  2. <a name="Bl7xZ"></a>
  3. #### 2.2.6 window_handles获取所有窗口
  4. - `浏览器对象.window_handles`属性可以获取当前浏览器中所有的窗口对象。
  5. - 示例:先访问英雄联盟官网,然后打印窗口列表,接着点击游戏资料,再打印窗口列表。
  6. ```python
  7. from selenium import webdriver
  8. from selenium.webdriver import ActionChains
  9. from selenium.webdriver.common.by import By
  10. import time
  11. browser = webdriver.Chrome()
  12. action = ActionChains(browser)
  13. browser.get("https://lol.qq.com/main.shtml")
  14. print(browser.window_handles) # ['CDwindow-8BC3504BA976BA5FA5A14FFC9DBF696A']
  15. time.sleep(3)
  16. game_info = browser.find_element(by=By.CSS_SELECTOR, value="#J_headNav > li > a")
  17. action.click(game_info).perform()
  18. print(browser.window_handles) # ['CDwindow-8BC3504BA976BA5FA5A14FFC9DBF696A', 'CDwindow-8074DC80214DEE82C03D784D4AF39CC3']

2.2.7 switch_to.window()切换窗口

  • 浏览器对象.switch_to.window(窗口字符串)函数可以切换selenium程序操作的窗口。
    • switch_to:切换到指定的窗口对象中。
    • window(窗口字符串):根据窗口字符串构造窗口对象。
  • 最新打开的窗口:window_handles中的元素顺序是按照窗口创建的先后顺序添加的,因此最新打开的创建一定是列表的最后一个元素,即window_handles[-1]
  • 示例1:先访问英雄联盟官网,接着点击游戏资料,然后在游戏资料窗口中访问百度。 ```python from selenium import webdriver from selenium.webdriver import ActionChains from selenium.webdriver.common.by import By import time

browser = webdriver.Chrome() action = ActionChains(browser)

browser.get(“https://lol.qq.com/main.shtml“) game_info = browser.find_element(by=By.CSS_SELECTOR, value=”#J_headNav > li > a”) action.click(game_info).perform()

time.sleep(2) # 为了实验效果,点击后先等待两秒,再访问百度。

切换操作窗口,并在切换后的窗口中打开网址。

browser.switch_to.window(browser.window_handles[-1]) # 一定要切换过来,否则当前窗口就是英雄联盟官网的窗口。 browser.get(“https://www.baidu.com/“)

  1. - 示例2:获取英雄联盟中所有英雄的个数。
  2. ```python
  3. from selenium import webdriver
  4. from selenium.webdriver import ActionChains
  5. from selenium.webdriver.common.by import By
  6. import time
  7. browser = webdriver.Chrome()
  8. action = ActionChains(browser)
  9. browser.get("https://lol.qq.com/main.shtml")
  10. game_info = browser.find_element(by=By.CSS_SELECTOR, value="#J_headNav > li > a")
  11. action.click(game_info).perform()
  12. # 英雄数据在游戏资料页面中,因此要切换浏览器操作的页面
  13. browser.switch_to.window(browser.window_handles[-1])
  14. time.sleep(3) # 切换后,程序停一会,让浏览器完成页面的加载后再获取数据,否则可能为空
  15. hero_list = browser.find_elements(by=By.CSS_SELECTOR, value=".hero-list > li")
  16. print(f"英雄的个数为:{len(hero_list)}")

2.2.8 text获取标签文本

  • 标签对象.text用于获取标签的文本,这个用法与beautifulsoup4中完全一致。
  • 示例:与2.2.10一起演示。

    2.2.9 back()返回上一个页面

  • browser.back()element.click()是一对相反的函数,当element.click()进入到一个新页面后,可以用browser.back()函数返回。

  • 示例:与2.2.10一起演示。

    2.2.10 get_attribute()获取标签属性

  • 标签对象.get_attribute(属性名)函数用于获取一个标签中指定属性名的属性值。

  • 示例:获取英雄联盟中所有英雄的名字、技能、职业、位置、胜率、BAN率、登场率、难度。 ```python import openpyxl import time from selenium import webdriver from selenium.webdriver.common.by import By

heroes_info = [ [“英雄名称”, “技能”, “职业”, “位置”, “胜率”, “BAN率”, “登场率”, “难度”] ] # 用于存储所有英雄数据

构建浏览器对象并访问英雄联盟首页。

browser = webdriver.Chrome() browser.get(“https://lol.qq.com/main.shtml“) time.sleep(2)

点击进入游戏资料页面,并切换窗口

game_info = browser.find_element(by=By.CSS_SELECTOR, value=”#J_headNav > li > a”) game_info.click() browser.switch_to.window(browser.window_handles[-1]) time.sleep(2)

获取英雄列表

hero_list = browser.find_elements(by=By.CSS_SELECTOR, value=”.hero-list > li”) for i in range(1, len(hero_list) + 1):

  1. # 进入详情页
  2. hero_li = browser.find_element(by=By.CSS_SELECTOR, value=f".hero-list > li:nth-child({i})")
  3. hero_li.click()
  4. time.sleep(2)
  5. try:
  6. # 有些在重做的英雄详情页中没有没有数据,只有“继续浏览”和“返回列表”两个按钮。
  7. # 对于这样的英雄,我们不做处理。
  8. back_list = browser.find_element(by=By.CSS_SELECTOR, value=".solid-s")
  9. back_list.click()
  10. time.sleep(2)
  11. except Exception as e:
  12. # 若没有“继续浏览”和“返回列表”两个按钮的英雄,则被认为是正常英雄,需要获取数据。
  13. # 获取数据
  14. hero_name = browser.find_element(by=By.CSS_SELECTOR, value=".hero-name").text
  15. skills_element = browser.find_elements(by=By.CSS_SELECTOR, value=".spells-list > .spells > img")
  16. skills_name = "、".join([skill.get_attribute("alt") for skill in skills_element])
  17. occupation = browser.find_element(by=By.CSS_SELECTOR, value=".hero-type").text.strip()
  18. position_list = browser.find_elements(by=By.CSS_SELECTOR, value=".filter-box > div:nth-child(2) > a")
  19. position = "、".join([i.text for i in position_list])
  20. win_rate = browser.find_element(by=By.CSS_SELECTOR, value=".hero-intro .win-text").text.strip()
  21. ban_rate = browser.find_element(by=By.CSS_SELECTOR, value=".ban-text").text.strip()
  22. appear_rate = browser.find_element(by=By.CSS_SELECTOR, value=".show-text").text.strip()
  23. difficulty = f"{len(browser.find_elements(by=By.CSS_SELECTOR, value='.light'))}星"
  24. row_data = [hero_name, skills_name, occupation, position, win_rate, ban_rate, appear_rate, difficulty]
  25. heroes_info.append(row_data)
  26. print(row_data)
  27. # 退出当前英雄的详情页,并准备点击下一个英雄的详情页。
  28. browser.back()
  29. time.sleep(2)

将爬取的数据存储到Excel中

wb = openpyxl.Workbook() wb.worksheets[0].title = “英雄信息” hero_ws = wb[“英雄信息”]

for line, line_data in enumerate(heroes_info): for column, data in enumerate(line_data): hero_ws.cell(line + 1, column + 1, data)

wb.save(“./excel/英雄联盟.xlsx”)

  1. <a name="xBeDD"></a>
  2. #### 2.2.11 move_to_element()移动鼠标到指定元素
  3. - 让鼠标移动到目标标签上方:`鼠标对象.move_to_element(标签对象).perform()`
  4. - 示例:进入[https://lol.qq.com/main.shtml](https://lol.qq.com/main.shtml)官网,点击游戏资料 >> 装备列表,当鼠标悬浮在每个装备上时,会出现关于装备的隐藏数据,获取这些数据。
  5. ```python
  6. import time
  7. from selenium import webdriver
  8. from selenium.webdriver import ActionChains
  9. from selenium.webdriver.common.by import By
  10. browser = webdriver.Chrome()
  11. mouse = ActionChains(browser)
  12. browser.get("https://lol.qq.com/main.shtml")
  13. time.sleep(2)
  14. # 点击进入游戏资料页面,并切换窗口
  15. game_info = browser.find_element(by=By.CSS_SELECTOR, value="#J_headNav > li > a")
  16. mouse.click(game_info).perform()
  17. browser.switch_to.window(browser.window_handles[-1])
  18. time.sleep(2)
  19. # 进入装备页面
  20. equip_list_page = browser.find_element(by=By.CSS_SELECTOR, value=".left-menu-nav > li:nth-child(6)")
  21. mouse.click(equip_list_page).perform()
  22. time.sleep(2)
  23. equip_list = browser.find_elements(by=By.CSS_SELECTOR, value=".equipment-list > li")
  24. for equip in equip_list:
  25. mouse.move_to_element(equip).perform() # 移动鼠标到各个装备上悬浮,以此获得隐藏数据。
  26. time.sleep(1)
  27. # 鼠标移动后,隐藏数据会显示出来,因此可获取数据。
  28. equip_name = equip.find_element(by=By.CSS_SELECTOR, value=".prop-name").text
  29. equip_price = equip.find_element(by=By.CSS_SELECTOR, value=".price > span").text
  30. synthesis_price = browser.find_element(by=By.CSS_SELECTOR, value=".component-dialog .price").text
  31. equip_desc = browser.find_element(by=By.CSS_SELECTOR, value=".component-dialog .desc").text
  32. print(f"装备名称:{equip_name}")
  33. print(f"装备购买价格:{equip_price}")
  34. print(f"合成价格:{synthesis_price}")
  35. print(f"装备描述:{equip_desc}")
  36. print("=" * 20)

03. 项目