01. requests模拟上网

1.1 requests模块介绍与安装

  • requests模块用于模拟浏览器发送请求。
  • requests是一个数据获取模块,常配合BeautifulSoup模块完成数据提取。
  • requests模块可以通过pip工具安装: ```python

    命令行安装

    pip install requests

Jupyter Notebook安装

!pip install requests

  1. <a name="lMzhb"></a>
  2. ### 1.2 requests模拟用户上网
  3. <a name="IavNU"></a>
  4. #### 1.2.1 发送GET请求
  5. - 爬虫程序中的请求一般都是GET请求,因为爬虫一般就是请求数据,很少需要提交数据。
  6. - 可以通过requests模块中的`get()`函数发送一个GET请求,`get()`函数有以下三个常用参数:
  7. - url:服务器地址/资源路径。
  8. - headers:请求头,以字典形式定义。(若没有则可以不设置)
  9. - params:请求体,以字典形式定义。(若没有则可以不设置)
  10. - 示例:模拟在百度中搜索关键字“pip镜像源”。
  11. ```python
  12. import requests
  13. # 发送请求
  14. # 相当于在浏览器网址中输入https://www.baidu.com/s?wd=pip镜像源
  15. resp = requests.get(
  16. url="https://www.baidu.com/s",
  17. params={"wd": "pip镜像源"} # 百度时,搜索栏中输入的内容会以wd的值的形式被添加到请求参数中。
  18. ) # requests.get()函数的返回内容就是HTTP响应,可以用一个变量去接收。
  19. # 输出响应的内容
  20. print(resp.text)

1.2.2 发送POST请求

  • 可以通过requests模块中的post()函数发送一个POST请求,post()函数有以下三个常用参数:
    • url:服务器地址/资源路径。
    • headers:请求头,以字典形式定义。
    • data:请求数据,以字典形式定义。
  • 除了请求数据形参名是data外,其余与get()完全一致。

    1.3 HTTP响应的常用属性

    1.3.1 headers获取请求头

  • response.headers可用于获取响应头,返回的是JSON格式的数据。 ```python import requests

resp = requests.get(url=”https://www.baidu.com/s“, params={“wd”: “pip镜像源”}) print(resp.headers) “”” 运行结果(): { “Accept-Ranges”: “bytes”, “Cache-Control”: “no-cache”, “Connection”: “keep-alive”, “Content-Length”: “227”, “Content-Type”: “text/html”, “Date”: “Mon, 18 Jul 2022 14:50:08 GMT”, “P3p”: “CP=\” OTI DSP COR IVA OUR IND COM \”, CP=\” OTI DSP COR IVA OUR IND COM \””, “Pragma”: “no-cache”, “Server”: “BWS/1.1”, “Set-Cookie”: “BD_NOT_HTTPS=1; path=/; Max-Age=300, BIDUPSID=D9610EE3B4E5D43B0BF376D268337689; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com, PSTM=1658155808; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com, BAIDUID=D9610EE3B4E5D43B79D00C64C57F6421:FG=1; max-age=31536000; expires=Tue, 18-Jul-23 14:50:08 GMT; domain=.baidu.com; path=/; version=1; comment=bd”, “Strict-Transport-Security”: “max-age=0”, “Traceid”: “1658155808093754522611963952688037033994”, “X-Frame-Options”: “sameorigin”, “X-Ua-Compatible”: “IE=Edge,chrome=1” } “””

  1. <a name="HWIgd"></a>
  2. #### 1.3.2 status_code获取响应状态码
  3. - `response.status_code`可用于获取响应状态码。
  4. ```python
  5. import requests
  6. resp = requests.get(url="https://www.baidu.com/s", params={"wd": "pip镜像源"})
  7. print(resp.status_code) # 200

1.3.3 text/content获取响应体

  • response.text可用于以字符串的形式获取响应的体。 ```python import requests

resp = requests.get(url=”https://www.baidu.com/s“, params={“wd”: “pip镜像源”}) print(resp.text)

  1. - `response.content`可用于以字节的形式获取响应的体。
  2. ```python
  3. import requests
  4. resp = requests.get(url="https://www.baidu.com/s", params={"wd": "pip镜像源"})
  5. print(resp.content)

1.3.4 json()以JSON解析响应体

resp = requests.get( url=”https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20“,

  1. # 豆瓣有418,因此需要填充请求头。
  2. # 这在1.5.2中有详细解释。
  3. headers={
  4. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'
  5. }

) print(type(resp.text)) # print(resp.text) # {“subjects”:[{“episodes_info”:””,”rate”:”7.2”,”cover_x”:3 ……

  1. - 而若用`响应体.json()`函数获取,则会自动完成解析。(在Python中,JSON会被解析成字典数据)
  2. ```python
  3. import requests
  4. resp = requests.get(
  5. url="https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20",
  6. headers={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 Edg/108.0.1462.46'}
  7. )
  8. print(type(resp.json())) # <class 'dict'>
  9. print(resp.json()) # {'subjects': [{'episodes_info': '', 'rate': '7.2', ……
  • 并且在像Jupyter Notebook这种IDE中,还会有良好的可读性。

image.png

1.4 requests模拟下载文件

1.4.1 模拟下载图片

  • 模拟图片下载实现思路:
    • 在网络上随便找一张图片,然后右键图片复制图片地址。
    • 对这张图片发起GET请求,然后判断请求的响应码是否为200(即请求是否成功)。
    • 若响应的状态码是200,则将字节数据保存到本地文件中即可。
    • 否则,提示用户请求失败。
  • 代码实现: ```python import requests

resp = requests.get(url=”https://img1.baidu.com/it/u=223288250,1771099662&fm=253&fmt=auto&app=138&f=JPEG?w=800&h=500“) if resp.status_code == 200: with open(“./image/动漫.png”, “wb”) as file: file.write(resp.content) else: print(f”数据请求失败,状态码为{resp.status_code}”)

  1. - 在了解了基本的图片爬取之后,还可以批量爬取一些具有相关性的图片:
  2. - 比如王者荣耀中瑶这个英雄的资料页面为:[https://pvp.qq.com/web201605/herodetail/505.shtml](https://pvp.qq.com/web201605/herodetail/505.shtml),在这个页面中我们可以看到瑶有好几张皮肤。
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670665027311-6b194562-f8c3-4761-8458-60bad789ba0a.png#averageHue=%23263834&clientId=u406dbef4-e5f9-4&from=paste&height=119&id=ua2ae0164&originHeight=175&originWidth=737&originalType=binary&ratio=1&rotation=0&showTitle=false&size=162988&status=done&style=none&taskId=u0c41839e-aec8-4064-92b7-bbde63a48a6&title=&width=500)
  4. - 通过鼠标小手工具可以找到这五个皮肤的相关源码,发现皮肤的大图连接就在其中。
  5. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1670665102752-ef2c47b0-5366-4d8c-9202-3508ea9d1373.png#averageHue=%23fdfdfd&clientId=u406dbef4-e5f9-4&from=paste&height=269&id=ue9a774e0&originHeight=735&originWidth=2182&originalType=binary&ratio=1&rotation=0&showTitle=false&size=117095&status=done&style=none&taskId=uc1032244-a7a2-42ef-b95d-122de92be32&title=&width=800)
  6. - 可以手动将这些大图的链接提取出来:
  7. > 鹿灵守心:http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-1.jpg
  8. > 森:http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-2.jpg
  9. > 遇见神鹿:http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-3.jpg
  10. > 时之祈愿:http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-4.jpg
  11. > 时之愿境:http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-5.jpg
  12. - 通过简单观察,可以发现这五张图的下载链接就是`http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-{1~5}.jpg`,那么借助一个循环就可以很容易的实现批量下载。
  13. ```python
  14. import requests
  15. for i in range(1, 6):
  16. img_url = f"http://game.gtimg.cn/images/yxzj/img201606/skin/hero-info/505/505-bigskin-{i}.jpg"
  17. img_resp = requests.get(img_url)
  18. if img_resp.status_code == 200:
  19. with open(f"./瑶/皮肤{i}.png", 'wb') as img_file:
  20. img_file.write(img_resp.content)
  21. else:
  22. print(f"皮肤{i}请求失败")

1.4.2 音频视频爬取

下载音频

audio_resp = requests.get(“http://dl.stream.qqmusic.qq.com/C400002etlBr14YnDT.m4a?guid=5323009909&vkey=B0D13363C2934707207DAD48A69C5A10F9E2094A484AF07C66B9699D612624E65F6070F791DD9704C23DE3B88129E16A067E867B2EFBB743&uin=850340745&fromtag=120032“) if audio_resp.status_code == 200: with open(“./音视频/audio.mp3”, ‘wb’) as audio_file: audio_file.write(audio_resp.content) else: print(“音频请求失败”)

下载视频

video_resp = requests.get(“https://ks-xpc20.xpccdn.com/2f5f76c0-4132-4feb-952b-6118e1f10666.mp4“) if video_resp.status_code == 200: with open(“./音视频/video.mp4”, ‘wb’) as video_file: video_file.write(video_resp.content) else: print(“视频请求失败”)

  1. <a name="LaufS"></a>
  2. ### 1.5 基础反爬与应对方式
  3. <a name="ABEQ4"></a>
  4. #### 1.5.1 反扒解决方案之填充请求头
  5. - 情景复现:
  6. - 现有一个图片网站:[https://pixivic.com/?VNK=a4e45edf](https://pixivic.com/?VNK=a4e45edf),访问这个网站,网站中的图片基本都能正常下载。
  7. - 但右键图片复制图片地址后,将图片的URL放到`requests.get()`中,得到HTTP响应码为403,甚至用其他浏览器直接访问这个图片的URL得到的也是403。
  8. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1658194405668-ce06c001-34cd-4fbd-aef7-2f81734aa35c.png#averageHue=%23f5f5f4&clientId=u8e697302-1ed4-4&from=paste&height=108&id=u62d82448&originHeight=128&originWidth=476&originalType=binary&ratio=1&rotation=0&showTitle=false&size=5513&status=done&style=none&taskId=ucea9f71f-fb5c-4972-821e-a230e1aa50d&title=&width=400)
  9. - 原因分析:
  10. - 有些资源从原生网站一步步操作可以正常访问,但直接使用URL却无法访问。
  11. - 这很有可能是由于在获取资源之前,该网站的服务器返回了一些Cookie和一些特殊的标记信息。
  12. - 在客户端浏览器正式请求资源时,发送的HTTP请求头中会加带有这些Cookie和标记信息。
  13. - 若服务器接收到的请求中有这些Cookie和标记信息,则响应码200且正常返回资源,否则就响应403。
  14. - 由于`requests.get()`中并没有填充请求头,用新浏览器访问这个URL时请求头中也没有这些信息。故当服务器接收到HTTP请求时,在里面无法看到这些Cookie和标记信息,因此响应403。
  15. - 解决方案:填充请求头。
  16. - 先用浏览器正常访问网站(访问时用F12调出Network进行监控),捕获HTTP请求数据(重要的是请求头)。
  17. - 将HTTP请求数据一点点填充到`get()`函数的header属性中,直到`resp.status_code`为200为止。
  18. - 但一般来说header中填充Cookie(用户令牌,类似于人的身份证)、Host(主机地址)、Referer(上一个页面)、User-Agent(用户身份)即可。
  19. - 添加顺序(建议,并不强制):先加User-Agent;若还是请求失败则添加Referer;若还是请求失败则添加Host;最后还是不行,则把Cookie加上。
  20. - referer常用于表示网站信息的来源,即数据是从哪个网站定向来的。
  21. - 这个图片爬取会造成403的主要原因就是请求头中缺少了referer。
  22. - 没有影响的字段(即不用管的字段):
  23. - 带冒号`:`的,如`:authority`、`:method`、`:path`、`:scheme`。
  24. - accept开头的,如`accept`、`accept-encoding`、`accept-language`。
  25. - sec开头的,如`sec-ch-ua`、`sec-ch-ua-mobile`、`sec-ch-ua-platform`、`sec-fetch-dest`、`sec-fetch-mode`、`sec-fetch-site`、`sec-fetch-user`。
  26. - 代码实现:用requests模块下载[https://pixivic.com/?VNK=a4e45edf](https://pixivic.com/?VNK=a4e45edf)中的一张图片。
  27. ```python
  28. import requests
  29. resp = requests.get(
  30. url="https://acgpic.net/c/540x540_70/img-master/img/2022/07/15/00/00/27/99727021_p0_master1200.jpg",
  31. headers={
  32. 'cookie': '_gid=GA1.2.1429353220.1658194205; '
  33. '__gads=ID=e3f93c65577b06e5-2281920d3bd5006c:T=1658194205:RT=1658194205:S=ALNI_MZzMTWGTGvjD5_qsfN3CNc4Vi2eug; '
  34. '__gpi=UID=000007d89c4eb20a:T=1658194205:RT=1658194205:S=ALNI_MaBfL_YHixfFr5O7P2zS1hpqdcO5g; '
  35. '_gat_gtag_UA_158701012_2=1; '
  36. '_ga_4FH141V9X0=GS1.1.1658194204.1.1.1658198544.0; '
  37. '_ga=GA1.1.625847025.1658194204',
  38. 'referer': 'https://pixivic.com/illusts/99726976?VNK=ff84c79b',
  39. 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36'
  40. }
  41. )
  42. if resp.status_code == 200:
  43. with open("./image/动漫.png", "wb") as file:
  44. file.write(resp.content)
  45. else:
  46. print(f"数据请求失败,状态码为{resp.status_code}")

1.5.2 状态码418解决方案

  • 情景复现:
    • 豆瓣电影 Top 250是国内一个著名的电影评分网站,网址为:https://movie.douban.com/top250
    • 但是若直接用requests模块对该网站发送GET请求,其响应状态码一定是418。 ```python import requests

resp = requests.get(url=”https://movie.douban.com/top250“) print(resp) #

  1. - 原因分析:网站监测到你的HTTP请求来源于一个Python爬虫程序。
  2. - Network中监测的到有:Name请求资源名称、Status响应状态码、Type响应的资源类型、Initiator请求的发起者。
  3. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2692415/1658209801951-e8b39713-1117-4d9f-9315-83822d043285.png#averageHue=%23f9f9f9&clientId=u8e697302-1ed4-4&from=paste&height=178&id=u0ddc226f&originHeight=178&originWidth=731&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15203&status=done&style=none&taskId=u5694eac9-7b14-4ff6-a070-b3936381fb0&title=&width=731)
  4. - InitiatorOther固定为浏览器,如上图浏览器去请求豆瓣评分 Top 250网站的HTML文档top250(document)。然后top250下面其他资源的请求都是top250发起的。
  5. - 豆瓣评分 Top 250之所以可以在浏览器中200,而在requests程序中418,就是因为requests程序中的InitiatorPython而不是Other
  6. - 解决方案:在请求头中填充User-Agent信息,将requests程序伪装成浏览器再发送HTTP请求。
  7. - 请求头的User-Agent字段描述了发送请求的设备信息。
  8. - 这些设备信息主要是操作系统和浏览器的信息,借助这些信息可以将一个爬虫程序伪装成一次浏览器发送的HTTP请求。
  9. - 代码实现:
  10. ```python
  11. import requests
  12. resp = requests.get(
  13. url="https://movie.douban.com/top250",
  14. headers={
  15. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  16. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  17. 'Chrome/103.0.0.0 Safari/537.36'
  18. }
  19. )
  20. print(resp) # <Response [200]>

1.6 响应数据乱码的解决方式

1.6.1 响应数据乱码概述

  • 在请求获取response后,直接打印响应体的text,会发现数据可能会出现乱码。 ```python import requests

resp = requests.get( url=”https://pvp.qq.com/web201605/herolist.shtml“, headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) ‘ ‘AppleWebKit/537.36 (KHTML, like Gecko) ‘ ‘Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62’ } ) print(resp.text)

  1. <a name="x5dAz"></a>
  2. #### 1.6.2 encoding属性获取响应体编码
  3. - 使用响应的`encoding`属性可以获取到响应的编码格式。
  4. ```python
  5. import requests
  6. resp = requests.get(
  7. url="https://pvp.qq.com/web201605/herolist.shtml",
  8. headers={
  9. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  10. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  11. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
  12. }
  13. )
  14. print(resp.encoding) # ISO-8859-1
  • encoding属性提取编码的方式是从响应体中提取内容的编码,若响应头中没有设置内容编码,则默认为默认为ISO-8859-1。
  • 注意:ISO-8859-1编码无法识别中文字符。

    1.6.3 apparent_encoding属性从meta中获取编码

  • apparent_encoding属性用于从响应内容中提取编码编码,即提取<head>标签中的<meta charset="XXX">的值。 ```python import requests

resp = requests.get( url=”https://pvp.qq.com/web201605/herolist.shtml“, headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) ‘ ‘AppleWebKit/537.36 (KHTML, like Gecko) ‘ ‘Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62’ } ) print(resp.apparent_encoding) # gbk

  1. <a name="XPrCN"></a>
  2. #### 1.6.4 乱码产生原因与解决方案
  3. - 原因:`response.encoding`和`response.apparent_encoding`的值不一致。
  4. - 解决方案一:将`response.encoding`的值改为`response.apparent_encoding`的值即可。
  5. ```python
  6. import requests
  7. resp = requests.get(
  8. url="https://pvp.qq.com/web201605/herolist.shtml",
  9. headers={
  10. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  11. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  12. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
  13. }
  14. )
  15. resp.encoding = resp.apparent_encoding
  16. print(resp.text) # 此时打印出来的内容就没有乱码了。
  • 解决方案二:先获取字节格式的响应数据,再将其按照response.apparent_encoding进行解码。 ```python import requests

resp = requests.get( url=”https://pvp.qq.com/web201605/herolist.shtml“, headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) ‘ ‘AppleWebKit/537.36 (KHTML, like Gecko) ‘ ‘Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62’ } ) print(resp.content.decode(resp.apparent_encoding))

  1. <a name="fDieB"></a>
  2. ## 02. BeautifulSoup4爬虫数据转换工具
  3. <a name="RLorx"></a>
  4. ### 2.1 BS4的介绍与安装
  5. - BeautifulSoup4是一个用于将爬虫数据转换成HTML文档的第三方工具。
  6. - BeautifulSoup4可以通过pip工具进行安装:
  7. ```python
  8. # 命令行安装
  9. pip install beautifulsoup4
  10. # Jupyter Notebook安装
  11. !pip install beautifulsoup4

2.2 BeautifulSoup()转换文档

  • 函数描述:BeautifulSoup(markup, features)
    • markup:字符串形式的标记语言的代码,或者类似文件的对象。
    • features:指定markup解析的格式,可以是以下两种类型:
      • 解析器的名称(建议使用):lxml、lxml-xml、html.parser、html5lib。
      • 要使用的标记类型:html、html5、xml。
  • 示例:获取”豆瓣电影 Top 250“主页的数据,并用bs4转换成HTML。 ```python import requests from bs4 import BeautifulSoup # 从bs4库中导入BeautifulSoup模块

resp = requests.get( url=”https://movie.douban.com/top250“, headers={ ‘User-Agent’: ‘Mozilla/5.0 (Windows NT 10.0; Win64; x64) ‘ ‘AppleWebKit/537.36 (KHTML, like Gecko) ‘ ‘Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62’ } ) if resp.status_code == 200: data = resp.text # 以字符形式获取HTTP响应的数据。 html_doc = BeautifulSoup(data, “html.parser”) # 将响应数据解析为HTML数据。 print(html_doc) else: print(f”数据请求失败,状态码为:{resp.status_code}”)

  1. <a name="vpd3p"></a>
  2. ### 2.3 标签对象的常用方法
  3. <a name="nX9C9"></a>
  4. #### 2.3.1 select()选择所有匹配元素(选择器)
  5. - 函数格式:`文档对象或者是先辈标签元素.select(选择器)`
  6. - 函数作用:从文档对象或者是先辈标签元素中,用选择器找出所有匹配的元素,并封装成列表。
  7. - 示例:获取”豆瓣电影 Top 250“中所有的电影信息。
  8. ```python
  9. # 开头的代码参考2.1 BeautifulSoup()转换文档。
  10. if resp.status_code == 200:
  11. html_doc = BeautifulSoup(resp.text, "html.parser") # 将响应数据转换为HTML数据。
  12. movie_all_info = html_doc.select(".grid_view > li") # 分析页面结构,然后定义选择器匹配结果
  13. print(movie_all_info)
  14. else:
  15. print(f"数据请求失败,状态码为:{resp.status_code}")
  • 快速匹配元素代码小技巧:右键页面中需要查看源代码的元素,点击检查。

image.png

  • 弹出的元素中会自动定位到需要的元素的源代码所在位置。

image.png

2.3.2 select_one()选择第一个匹配元素(选择器)

  • 函数格式:文档对象或者是先辈标签元素.select_one(选择器)
  • 函数作用:
    • 与select()唯一的不同在于select()返回的是匹配到的是所有元素,而select_one()则只返回匹配到的第一个元素。
    • 简单来说就是select(选择器)[0]
  • 示例:获取”豆瓣电影 Top 250“中第一部电影的信息。

    1. # 开头的代码参考2.1 BeautifulSoup()转换文档。
    2. if resp.status_code == 200:
    3. html_doc = BeautifulSoup(resp.text, "html.parser")
    4. movie_all_info = html_doc.select_one(".grid_view > li")
    5. print(movie_all_info)
    6. else:
    7. print(f"数据请求失败,状态码为:{resp.status_code}")

    2.3.3 text获取标签值

  • 属性格式:标签对象.text

  • 函数用途:用于获取标签对象里,开始标签和结束标签包裹的标签值。
  • 示例:获取所有电影的电影名称、电影评分、评价人数。

    1. # 开头的代码参考2.1 BeautifulSoup()转换文档。
    2. if resp.status_code == 200:
    3. html_doc = BeautifulSoup(resp.text, "html.parser")
    4. movie_all_info = html_doc.select(".grid_view > li") # 获取电影列表中的所有元素。
    5. # 遍历电影列表(无序列表)中的每一项,然后依次进行分析取值。
    6. for movie_li in movie_all_info:
    7. movie_name = movie_li.select_one(".title").text # 电影名字存在li中的title类的span中
    8. movie_score = movie_li.select_one(".rating_num").text # 电影评分存在li中的rating_num类的span中
    9. evaluators = movie_li.select_one(".star > span:last-child").text # 评价人数存储在li中的star类中的最后一个span中
    10. print(f"{movie_name}评分为{movie_score}分,评价人数:{evaluators.strip('人评价')}")
    11. else:
    12. print(f"数据请求失败,状态码为:{resp.status_code}")

    2.3.4 attrs获取标签属性

  • 属性格式:标签对象.attrs

  • 函数用途:以字典形式返回标签中所有属性及其属性值。

    • 如这样一个标签元素:

      1. <img class="pic" src="../image/picture.jpg" width="100px" alt="This is a picture.">
    • attrs的内容为:

      1. {'class': ['pic'], 'src': '../image/picture.jpg', 'width': '100px', 'alt': 'This is a picture.'}
  • 示例1:获取所有电影的电影名称及其作者。

    • 需求分析:
      • 电影名称在2.2.3中已经实现过了。
      • 对于电影导演,我们可以点击电影标题信息,进入详情页。
        • 虽然首页中也有导演信息。
        • 但是首页的数据通过检查可以发现是一段连续的p段落,因此就算数据获取后还要经过索引、切片等处理。

image.png

  1. - 然后在详情页中可以很轻松的获取导演的信息。

image.png

  • 代码实现:

    1. # 开头的代码参考2.1 BeautifulSoup()转换文档。
    2. if resp.status_code == 200:
    3. html_doc = BeautifulSoup(resp.text, "html.parser")
    4. movie_all_info = html_doc.select(".grid_view > li") # 获取每个电影的信息。
    5. # 遍历每个电影信息,找到每部电影的名称与详情页链接。
    6. for movie_li in movie_all_info:
    7. movie_name = movie_li.select_one(".title").text
    8. detail_url = movie_li.select_one(".hd > a:first-child").attrs["href"]
    9. # 对详情页再发起一次请求,获取导演信息。
    10. time.sleep(random.randint(2, 5)) # 在请求前进行短暂休眠,请求太快会被服务器认为是攻击行为。
    11. resp = requests.get(
    12. url=detail_url,
    13. headers={
    14. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
    15. 'AppleWebKit/537.36 (KHTML, like Gecko) '
    16. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
    17. }
    18. )
    19. detail_html = BeautifulSoup(resp.text, "html.parser")
    20. author_name = detail_html.select_one("#info > span:first-child > .attrs > a").text
    21. print(f"电影《{movie_name}》作者:{author_name}")
    22. else:
    23. print(f"数据请求失败,状态码为:{resp.status_code}")
  • 示例2:获取所有电影的电影名称、编剧、电影类型、上映时间。

    1. # 开头的代码参考2.1 BeautifulSoup()转换文档。
    2. if resp.status_code == 200:
    3. html_doc = BeautifulSoup(resp.text, "html.parser")
    4. movie_all_info = html_doc.select(".grid_view > li") # 获取每个电影的信息。
    5. # 遍历每个电影信息,找到每部电影的名称与详情页链接。
    6. for movie_li in movie_all_info:
    7. # 获取电影名称与详情页链接。
    8. movie_name = movie_li.select_one(".title").text
    9. detail_url = movie_li.select_one(".hd > a:first-child").attrs["href"]
    10. # 对详情页再发起一次请求,获取编剧、电影类型、上映时间。
    11. time.sleep(random.randint(2, 5))
    12. resp = requests.get(
    13. url=detail_url,
    14. headers={
    15. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
    16. 'AppleWebKit/537.36 (KHTML, like Gecko) '
    17. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
    18. }
    19. )
    20. detail_html = BeautifulSoup(resp.text, "html.parser")
    21. # 编剧信息
    22. scriptwriter_in_html = detail_html.select("#info > span:nth-child(3) > .attrs > a")
    23. scriptwriter_list = [i.text for i in scriptwriter_in_html]
    24. scriptwriter = "、".join(scriptwriter_list)
    25. # 电影类型信息
    26. movie_type_in_html = detail_html.select("span[property='v:genre']")
    27. movie_type_list = [i.text for i in movie_type_in_html]
    28. movie_type = "、".join(movie_type_list)
    29. # 上映时间信息
    30. release_in_html = detail_html.select("span[property='v:initialReleaseDate']")
    31. release_list = [i.text for i in release_in_html]
    32. release = "、".join(release_list)
    33. print(f"电影:《{movie_name}》\n编剧:{scriptwriter}\n电影类型:{movie_type}\n上映时间:{release}\n")
    34. else:
    35. print(f"数据请求失败,状态码为:{resp.status_code}")

    2.3.5 find_all()选择符合条件的元素

  • 函数格式:文档对象或者是先辈标签元素.find_all(name=标签名称, attrs={属性键值对}, text=标签的内容)

    • 三个参数不需要全写,只需要根据匹配条件挑选其中一个或多个填写即可。
    • 但一般来说,name属性指定的标签名称是要写的。
  • 函数作用:
    • 筛选出指定名称,指定属性,指定内容的所有标签对象,并封装成列表返回。
    • select()select_one()类似,find_all()也有一个兄弟函数find(),所有参数与find_all()一致。用于筛选出指定名称,指定属性,指定内容的所有标签对象中的第一个,即find()等价于find_all()[0]
  • 示例:与2.2.6一起举例。

    2.3.6 next_sibling获取下一个平级的内容

  • 函数格式:文档对象或者是先辈标签元素.next_sibling

  • 函数作用:筛选出与当前标签对象平级的(即兄弟关系)下一个标签或文本内容。
  • 补充:
    • next_element属性:只筛选出与当前标签对象平级的下一个标签,不筛选出文本内容。
    • previous_sibling属性:筛选出与当前标签对象平级的上一个标签或文本内容。
  • 示例:从详情页中爬出当前电影的语言数据及制片国家/地区数据。
    • 需求分析:
      • 以肖申克的救赎为例,其语言数据“ 英语”和<span class="pl">语言:</span>是平级的兄弟关系。
      • 影片的语言存放在<div id="info">中,该标签的子元素众多不好定位。最简单的方式就是先定位到<span class="pl">语言:</span>,然后再用next_sibling即可获取到语言数据。

image.png

  1. - 制片国家/地址的数据格式与语言数据的格式也是一样的,因此处理思路也是一样的。
  • 代码实现:

    1. # 开头的代码参考2.1 BeautifulSoup()转换文档。
    2. if resp.status_code == 200:
    3. html_doc = BeautifulSoup(resp.text, "html.parser")
    4. movie_all_info = html_doc.select(".grid_view > li")
    5. for movie_li in movie_all_info:
    6. movie_name = movie_li.select_one(".title").text
    7. detail_url = movie_li.select_one(".hd > a:first-child").attrs["href"]
    8. time.sleep(random.randint(2, 5))
    9. resp = requests.get(
    10. url=detail_url,
    11. headers={
    12. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
    13. 'AppleWebKit/537.36 (KHTML, like Gecko) '
    14. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
    15. }
    16. )
    17. detail_html = BeautifulSoup(resp.text, "html.parser")
    18. # 获取语言数据
    19. language_ele = detail_html.find(name="span", text="语言:") # 定位详情页中的<span class="pl">语言:</span>标签。
    20. language_type = language_ele.next_sibling.text.strip() # 使用next_sibling属性获取语言数据。
    21. # 获取制片国家/地址数据
    22. nation_ele = detail_html.find(name="span", text="制片国家/地区:")
    23. nation = nation_ele.next_sibling.text.strip()
    24. print(f"电影《{movie_name}》的语言类型为:{language_type},制片国家/地址数据:{nation}")
    25. else:
    26. print(f"数据请求失败,状态码为:{resp.status_code}")

    2.4 获取多页数据

  • 从“豆瓣电影 Top 250”底部可以看出,该网站共有250条电影数据,分成了10页显示。

image.png

for pageNumber in range(1, 11):

  1. # 每次请求前停一会,以防服务器识别成攻击流量。
  2. time.sleep(random.randint(2, 5))
  3. # 根据页码获取URL。
  4. start = (pageNumber - 1) * 25
  5. page_url = f"https://movie.douban.com/top250?start={start}&filter="
  6. print(f"第{pageNumber}页的数据:", "=" * 30)
  7. # 向指定页的URL发送HTTP请求
  8. resp = requests.get(
  9. url=page_url,
  10. headers={
  11. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  12. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  13. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
  14. }
  15. )
  16. # 如果响应成功,则爬取每一页中的基础电影数据。
  17. if resp.status_code == 200:
  18. html_doc = BeautifulSoup(resp.text, "html.parser")
  19. movie_all_info = html_doc.select(".grid_view > li")
  20. for movie_li in movie_all_info:
  21. movie_name = movie_li.select_one(".title").text # 电影名字存在li中的title类的span中
  22. movie_score = movie_li.select_one(".rating_num").text # 电影评分存在li中的rating_num类的span中
  23. evaluators = movie_li.select_one(".star > span:last-child").text # 评价人数存储在li中的star类中的最后一个span中
  24. print(f"{movie_name}评分为{movie_score}分,评价人数:{evaluators.strip('人评价')}")
  25. else:
  26. print(f"数据请求失败,状态码为:{resp.status_code}")
  1. <a name="be1Ln"></a>
  2. ## 03. 数据存储
  3. - 爬虫爬下来的数据可以存储到Excel文件、CSV文件、数据库中。
  4. - 示例:将“豆瓣电影 Top 250”中所有电影的电影名称、评分、评论人数、导演、编剧、类型、上映时间、语言、制片国家爬取下来,并保存到“./excel/top250.xlsx”文件中。
  5. ```python
  6. # 导入相关模块
  7. import openpyxl
  8. import requests
  9. from bs4 import BeautifulSoup
  10. import time
  11. import random
  12. # 创建工作簿,定位单元表。
  13. top250_wb = openpyxl.Workbook()
  14. top250_wb.worksheets[0].title = "电影信息表"
  15. movie_info_worksheet = top250_wb["电影信息表"]
  16. # 定义一个二维列表,首个元素为数据title,后面的是每个电影的数据。
  17. movie_info_list = [
  18. ["电影名称", "评分", "评论人数", "导演", "编剧", "类型", "上映时间", "语言", "制片国家"]
  19. ]
  20. # 用爬虫爬取数据,并封装成列表添加到二位列表中。(未完成)
  21. for page in range(10):
  22. # 请求前停2~3秒
  23. time.sleep(random.randint(2, 3))
  24. page_url = f"https://movie.douban.com/top250?start={page * 25}&filter="
  25. resp = requests.get(
  26. url=page_url,
  27. headers={
  28. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  29. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  30. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
  31. }
  32. )
  33. if resp.status_code == 200:
  34. print("=" * 20, f"第{page + 1}页数据", "=" * 20)
  35. html_doc = BeautifulSoup(resp.text, "html.parser")
  36. movies_base_info = html_doc.select(".grid_view > li")
  37. for movie_base_info in movies_base_info:
  38. movie_name = movie_base_info.select_one(".title").text # 电影名称
  39. movie_score = movie_base_info.select_one(".rating_num").text # 评分
  40. evaluators = movie_base_info.select_one(".star > span:last-child").text # 评论人数
  41. # 请求详情页,获取其他数据
  42. detail_url = movie_base_info.select_one(".hd > a:first-child").attrs["href"]
  43. time.sleep(random.randint(2, 5))
  44. detail_resp = requests.get(
  45. url=detail_url,
  46. headers={
  47. 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
  48. 'AppleWebKit/537.36 (KHTML, like Gecko) '
  49. 'Chrome/103.0.5060.114 Safari/537.36 Edg/103.0.1264.62'
  50. }
  51. )
  52. if detail_resp.status_code == 200:
  53. detail_html = BeautifulSoup(detail_resp.text, "html.parser")
  54. author = detail_html.select_one("#info > span:first-child > .attrs > a").text # 导演
  55. # 编剧
  56. scriptwriter_in_html = detail_html.select("#info > span:nth-child(3) > .attrs > a")
  57. scriptwriter_list = [i.text for i in scriptwriter_in_html]
  58. scriptwriter = "、".join(scriptwriter_list)
  59. # 类型
  60. movie_type_in_html = detail_html.select("span[property='v:genre']")
  61. movie_type_list = [i.text for i in movie_type_in_html]
  62. movie_type = "、".join(movie_type_list)
  63. # 上映时间
  64. release_in_html = detail_html.select("span[property='v:initialReleaseDate']")
  65. release_list = [i.text for i in release_in_html]
  66. release = "、".join(release_list)
  67. # 语言
  68. language_ele = detail_html.find(name="span", text="语言:")
  69. language_type = language_ele.next_sibling.text.strip()
  70. # 制片国家
  71. nation_ele = detail_html.find(name="span", text="制片国家/地区:")
  72. nation = nation_ele.next_sibling.text.strip()
  73. else:
  74. print(f"详情页数据请求失败,状态码为:{detail_resp.status_code}")
  75. row_data = [movie_name, movie_score, evaluators, author, scriptwriter, movie_type, release, language_type, nation]
  76. movie_info_list.append(row_data)
  77. print(row_data)
  78. print()
  79. else:
  80. print(f"数据请求失败,状态码为:{resp.status_code}")
  81. # 将二维列表中的数据写入到Excel文件中。
  82. for line, line_data in enumerate(movie_info_list):
  83. for column, data in enumerate(line_data):
  84. movie_info_worksheet.cell(line + 1, column + 1, data)
  85. # 保存数据
  86. top250_wb.save("./excel/top250.xlsx")