ToB企服应用市场:ToB评测及商务社交产业平台

标题: python爬虫入门(一) - requests库与re库,一个简朴的爬虫程序 [打印本页]

作者: 河曲智叟    时间: 2025-1-26 09:59
标题: python爬虫入门(一) - requests库与re库,一个简朴的爬虫程序
目录
web请求与requests库
1. web请求
1.1 客户端渲染与服务端渲染
1.2 抓包
1.3 HTTP状态代码
2. requests库
2.1 requests模块的下载
2.2 发送请求头与请求参数
2.3 GET请求与POST请求
GET请求的例子:
POST请求的例子:

3. 案例:爬取豆瓣电影榜
正则表达式与re库
1. 正则表达式
1.1 贪心匹配
1.2 惰性匹配
2. re库
2.1 compile() - 创建正则对象
2.2 match()与search() - 单个匹配
2.3 findall()与finditer() - 全部匹配
2.4 sub() - 更换
2.5 split() - 分割
捕捉组与非捕捉组
3. 案例:爬取豆瓣top250电影指定命据


web请求与requests库

1. web请求

web请求是指客户端服务端发送的请求,以此得到服务端提供的资源,或者与服务端进行交互。其中,客户端是发送请求的一方,通常指浏览器、移动应用等;服务端是响应的一方,指运行在服务器上的web应用程序。
爬虫属于客户端,通过向服务端发送web请求,获取服务端的数据。
下面介绍常见的两种web请求范例,这里暂不做详细介绍:

1.1 客户端渲染与服务端渲染


服务端将底子的HTML骨架和源代码打包发给客户端,客户端经加载源代码后,再进行请求数据和界面渲染。由于服务端发送的并非成熟的HTML文件,爬虫须要额外的开销来加载和实行源代码

服务端完成页面的渲染,并将经渲染的HTML文件发送给客户端,浏览器无需经过源文件的加载和实行,便可直接展示界面。由于服务端提供了HTML文件,爬虫可以直接获取完整的HTML文件,而无需等待加载源文件,因此服务端渲染对爬虫更为友爱
1.2 抓包

渲染方式不同,获取URL的方式也略有不同。
基本步骤:打开须要爬取的界面 - F12 - 刷新 - 网络 - 打开第一个数据包

第一个数据包(黑色方框)为数据,因此该网页为服务端渲染。从该数据包预览页中可以看到页面的全部信息。


此数据包的标头,存放着需爬取的URL。



第一个数据包是HTML骨架,没有完整的界面信息。


此时我们须要找到存放数据的包,这个为爬虫须要爬的内容。


应使用该数据包的URL:


1.3 HTTP状态代码



在抓包过程中,我们注意到表头有一行状态代码显示:“200 OK”,表示请求成功、并返回了资源。在平时生活中,也会遇到“404”、“304”等环境。“404”“304”与“200”一样,都属于HTTP的状态代码,用来表示HTTP请求的处置惩罚效果。这些状态代码被划分到了不同的范例,具有独特的意义。我们须要熟记每一类状态码的意义。
   1xx - 信息性状态码
  
  2xx - 成功状态码
  
  3xx - 重定向状态码
  
  4xx - 客户端错误状态码
  
  5xx - 服务器错误状态码
  
   2. requests库

requests是一个常用的python第三方库,常用于发送HTTP请求。requests库封装了底层的HTTP协议的细节,使得发送和响应HTTP请求变得十分轻松。
2.1 requests模块的下载

这里介绍采用下令行的方式下载:win+R - 打开cmd - 输入下令行
  1. pip install requests
复制代码
如果由于超时导致下载失败,可以考虑延长pip等待时间,通过--default timeout的方式设置超时时间。
  1. pip install requests --default-timeout=120
复制代码


这样,就下载成功了。
2.2 发送请求头与请求参数


一些网址的爬取要求加上请求头,这样可以让程序看起来更像真人。







一些URL链接过长,其原因是链接背面接了部分参数。可以通过发送请求参数,减短URL链接。一样寻常环境下,“?”后的数据为参数。
  1. import requests
  2. # 设置请求头
  3. headers = {'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'}
  4. #设置请求参数
  5. params ={
  6.     'q': '陈奕迅'
  7.     'qs': 'n'
  8.     'form': 'QBRE'
  9.     'sp': -1
  10.     'lq': 0
  11.     'pq': '陈奕迅'
  12.     'sc': '13-3'
  13.     'sk':''
  14.     'cvid': 'D6F23A59983048E485AA76E7A663ED49'
  15.     'ghsh': 0
  16.     'ghacc': 0
  17.     'ghpl':'' }
  18. #若不设置请求参数,url为:
  19. #https://cn.bing.com/search?q=%E9%99%88%E5%A5%95%E8%BF%85&qs=n&form=QBRE&sp=-1&lq=0&pq=%E9%99%88%E5%A5%95%E8%BF%85&sc=13-3&sk=&cvid=D6F23A59983048E485AA76E7A663ED49&ghsh=0&ghacc=0&g
  20. url="https://cn.bing.com/search"
复制代码
2.3 GET请求与POST请求

GET请求和POST请求是HTTP协议中最常用的两种请求方式。

GET请求的例子:

  1. import requests     #导入requests模块
  2. url = 'https://baike.baidu.com/item/陈奕迅/128029'    #获取url链接
  3. header={
  4.     "User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0"
  5. }
  6. resp = requests.get(url,headers=header)    #发送请求并用变量resp接收
  7.                         #添加user-agent表头,使看起来更像真人访问
  8. print(resp.text)
复制代码


运行可以看到,网页的编码出现在了运行效果处。这就是个简朴的爬虫程序。


我们已经成功向服务端发送请求并得到了响应。接下来则须要将获取到的数据写入HTML文件中:
  1. try:
  2.     resp = requests.get(url,headers=header)    #发送请求并用变量resp接收
  3.     #请求成功,将响应的内容存放至HTML文档中,追加方式为'w'(写入),写入格式为utf-8。
  4.     with open('eason.html','w',encoding='utf-8') as file:
  5.         file.write(resp.text)
  6.     print("请求成功,已将数据存放至HTML文件!")
  7. except Exception as e:
  8.     print("请求失败!")
复制代码
运行,将自送生成一个html文件。打开该文件即可浏览我们趴下来的页面。


POST请求的例子:

以百度翻译为例子。当输入信息后,会自动生成几个数据包。


筛选Fetch/XHR文档,找到存放服务端返回内容的数据包,即可得到对应的URL和数据表单。






  1. import requests     #导入requests模块
  2. url = 'https://fanyi.baidu.com/sug'    #获取url链接
  3. headers={
  4.     #模拟浏览器发送请求
  5.     "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0"
  6. }
  7. data={
  8.     #模拟表单数据,发送关键字apple
  9.     "kw":"apple"
  10. }
  11. #发送post请求
  12. resp=requests.post(url,headers=headers,data=data)
  13. #响应是json格式,可以直接解析并打印。
  14. print(resp.json())
复制代码
运行,即可获取返回的数据。

网页上显示的数据如下:




3. 案例:爬取豆瓣电影榜

找到存放豆瓣电影榜的数据包:


URL中,"?"后的数据为参数,可删去,而用负载中的数据表示这些参数




  1. import requests     #导入requests模块
  2. url='https://movie.douban.com/j/chart/top_list'
  3. #请求头
  4. headers={'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'}
  5. #请求参数
  6. params ={
  7.     'type': 11,
  8.     'interval_id': '100:90',
  9.     'action':'',
  10.     'start': 0,
  11.     'limit': 20
  12. }
  13. # 尝试发送 GET 请求
  14. try:
  15.     resp = requests.get(url, headers=headers, params=params)
  16.     print(resp.json())  # 尝试解析 JSON 数据
  17. except Exception:
  18.     print("请求失败!")
复制代码


正则表达式与re库

1. 正则表达式

正则表达式是一种表示字符串的基本语法,可以用来匹配字符串
比方,我们拿“\d”去匹配字符串“123ABC”,可以得到3种匹配效果:“\d”分别匹配1、2、3。


正则对象“a*.”可以用来匹配“aaaaab”:
“a*”中*可以匹配0次或多次前面元素a,即“a*”可以匹配“aaaaa”;而“.”可以匹配任意字符,即“.”可以匹配“b”。因此正则对象“a*.”可以匹配字符串“aaaaab”。
下面是一些正则表达式的常见用法:
\w
匹配单个字母/数字/下划线
\s
匹配单个空格
\d
匹配单个数字
\n
匹配单个换行符
\t
匹配单个制表符
^
匹配字符串的开始

匹配字符串的末端
\W
匹配非数字、字母、下划线
\D
匹配非数字
\S
匹配非空缺符
a|b
匹配a或b
()
分组,用于分组匹配或捕捉子字符串
[]
字符集,用于匹配括号内任意单个字符
.
匹配除换行符以外的任意单个字符
*
匹配前面元素0次或多次
+
匹配前面元素1次或多次

匹配前面元素0次或1次
{n}
匹配前面元素n次
{n,}
匹配前面元素n次或多次
{n,m}
匹配前面元素n次到m次
1.1 贪心匹配

贪心匹配是正则表达式中的一种匹配模式,他的思想是:尽大概匹配更长的字符串。如“\d+”就是一种贪心匹配模式,可以或许尽大概多的匹配字符串中的数字。在字符串“abc~12345~de_67_”中,对于正则对象“\d+”可以找到两种匹配:“12345”“67”,这两种匹配已是最长。


再如,“.*”也是一种贪心匹配模式,可以“a.*b”可以明白为:忽略a与b间的内容,找到最长的匹配。


1.2 惰性匹配

惰性匹配是正则表达式中的一种匹配模式,与贪心匹配相反,他的思想是:尽大概匹配更短的字符串。要想实现惰性匹配,只需在量词后加上?。如“\d+?”就是一种惰性匹配模式,可以或许尽大概少的匹配字符串中的数字。在字符串“abc~12345~de_67_”中,对于正则对象“\d+?”能找到的匹配是单个数字字符。因此,“\d”就是一种惰性匹配。


再如,“.*?”也是一种惰性匹配模式,可以“a.?*b”可以明白为:忽略a与b间的内容,找到最短的匹配。


2. re库

re是python的尺度库之一,用于正则表达式的处置惩罚
2.1 compile() - 创建正则对象

re.compile():用于创建新的正则对象。
  1. import re
  2. #匹配1个或多个字符
  3. pattern =  re.compile(r'\d+')
  4. # 匹配URL链接
  5. url_pattern = re.compile(
  6.     r'^(https?)://'  # 匹配 http、https 或 ftp
  7.     r'([a-zA-Z0-9.-]+)'  # 域名
  8.     r'(:\d+)?'  # 端口号
  9.     r'(/[^?\s]*)?'  # 路径
  10.     r'(\?[^#\s]*)?'  # 查询参数
  11.     r'(#\S*)?$'  # 锚点
  12. )
复制代码
2.2 match()与search() - 单个匹配

match()与search()均用于查找字符串的第一个匹配,区别在于:match是从字符串最开始处匹配,一旦这个字符串的最开始部分与正则表达式不匹配,无论背面是否有子字符串与之匹配,都将匹配失败;而search()只须要找到与正则表达式匹配的子字符串,无论这个子字符串是否在字符串开头

  1. import re
  2. pattern =  re.compile(r'\d+')
  3. text=['123abc456','abc456def']
  4. match=[re.match(pattern,text[0]),re.match(pattern,text[1])]
  5. for i in range(2):
  6.     if match[i]:
  7.         print(f"text[{i}]:匹配成功!{match[i].group()}")
  8.     else:
  9.         print(f"text[{i}]:匹配失败!")
  10.         
  11.         #text[0]:匹配成功!123
  12.         #text[1]:匹配失败!
复制代码

  1. import re
  2. pattern =  re.compile(r'\d+')
  3. test = "abc123de456"
  4. match=pattern.search(test)
  5. if match:
  6.     print(match.group())        #123
  7. else:
  8.     print("匹配失败!")
复制代码
2.3 findall()与finditer() - 全部匹配

findall()与finditer()都用于查找全部的匹配,但他们的返回值有所不同:findall()将返回与正则表达式匹配的子字符串列表,而finditer()则返回一个迭代器,每次迭代返回一个Match对象。关于Match对象的用法:
   match.group():返回匹配的字符串。
  match.start():返回匹配的起始位置。
  match.end():返回匹配的竣事位置。
  match.span():返回一个元组 (start, end),表示匹配的起始和竣事位置。
  
  1. import re
  2. pattern =  re.compile(r'\d+')
  3. test = "abc123de456"
  4. match=pattern.findall(test)
  5. if match:
  6.     print(match)                #['123', '456']
  7. else:
  8.     print("匹配失败!")
复制代码

  1. import re
  2. text="hello,python!"
  3. pattern = re.compile(r'\w+')
  4. matches=re.finditer(pattern,text)
  5. # 使用迭代器逐个处理匹配结果
  6. for match in matches:
  7.     print(f"Match found: {match.group()} at position {match.start()}-{match.end()}")
  8. #Match found: hello at position 0-5
  9. #Match found: python at position 6-12
复制代码
2.4 sub() - 更换

re.sub(pattern,str,text):用str更换text中匹配正则对象pattern的内容。
  1. import re
  2. pattern =  re.compile(r'\d+')
  3. test = "abc123de456"
  4. res=re.sub(pattern,"x",test)
  5.     print(res)             #abcxdex
复制代码
2.5 split() - 分割

re.split(pattern,text):依照正则对象pattern分割text,与pattern匹配的字符串作为分割边界。
  1. import re
  2. pattern =  re.compile(r'\d+')
  3. test = "abc123de456"
  4. result= re.split(pattern,test)
  5. print(result)       #['abc', 'de', '']
复制代码
捕捉组与非捕捉组


如,格式为YYYY-MM-DD的日期字符串,我们希望对其进行分组,从而能分别捕捉年、月、日。那么对该字符串的分组为:(YYYY)(MM)、(DD)对应的正则表达式为:(\d{4})-(\d{2})-(\d{2})
  1. import re
  2. pattern =  re.compile(r'(\d{4})-(\d{2})-(\d{2})')   #YYYY-MM-DD
  3. text='2025-01-01'
  4. match=re.match(pattern,text)
  5. if match:
  6.     #捕获不同分组
  7.     yyyy=match.group(1)
  8.     mm=match.group(2)
  9.     dd=match.group(3)
  10.     print(f"Year:{yyyy},Month:{mm},Date:{dd}")          #Year:2025,Month:01,Date:01
  11. else:
  12.     print("匹配失败!")
复制代码
在捕捉组中,可以添加“?<name>”来定名捕捉组,如:(?<name>pattern)

3. 案例:爬取豆瓣top250电影指定命据

要求爬取数据:电影名称、上映年份、国家、评分,并把数据存入csv文件中。


怎样用正则表达式表达爬取所需的格式
起首,须要找到数据存放的位置。


选择图上这个图标,点击“肖申克的救赎”,可以快速定位数据地点位置。找到数据地点位置后,我们找到它的最外层文件:可以看到,当光标停放在<li>这一行时,《肖申克的救赎》这部电影的全部数据都囊括在阴影处。往下可以找到须要爬去的数据。因此我们的正则表达式可以从"<li>"开始,接下来只须要表达式能特异性地识别到数据存放的位置。


依据上图,最后的正则表达式为:
(正则表达式不唯一)
  1. obj=re.compile(r'<li>.*?<div class="item">.*?'        #最外层及第二层文件
  2.            r'<span class="title">(?P<name>.*?)</span>.*?'        #电影名称
  3.            r'<br>(?P<year>.*?)&nbsp;/&nbsp;(?P<country>.*?)&nbsp;/.*?'        #年份、国家
  4.            r'<span class="rating_num" property="v:average">(?P<score>.*?)</span>', #评分
  5.            re.S)
复制代码
<li>.*?<div class="item">
.*? ”是一种惰性匹配*匹配任意字符,.*? 意味着要尽量少的匹配字符直到出现下一次匹配。这里可以明白为:忽略<li>与<div class="item">之间的内容。其他位置 “ .*? ”同理。
<span class="title">
精确匹配HTML的标签,这是电影名的开始处。其他处同理。
(?P<name>.*?)
这是一个捕捉组,采用了 (?P<name>…) 用于捕捉电影名称,并使其可以被name捕捉。其他处同理。
</span>
精确匹配HTML的标签,这是电影名的竣事位置。其他处同理。
 / 
HTML中相邻两项的分隔。
re.S
编译标志,也被称为DOTALL模式,使“ . ”可以匹配包罗换行符在内的任意字符。
最后的程序:
  1. import requests
  2. import re
  3. import csv
  4. url = "https://movie.douban.com/top250"                #URL链接
  5. headers = {                #请求头
  6.     'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 Edg/132.0.0.0'
  7. }
  8. try:
  9.     #发送请求
  10.     resp=requests.get(url,headers=headers)
  11.     #状态判断
  12.     if resp.status_code!=200:
  13.         raise Exception(f"状态码错误:{resp.status_code}")
  14.     #创建正则表达式
  15.     obj=re.compile(r'<li>.*?<div class="item">.*?'
  16.            r'<span class="title">(?P<name>.*?)</span>.*?'
  17.            r'<br>(?P<year>.*?)&nbsp;/&nbsp;(?P<country>.*?)&nbsp;/.*?'
  18.            r'<span class="rating_num" property="v:average">(?P<score>.*?)</span>',re.S)
  19.    
  20.     result=obj.finditer(resp.text)
  21.    
  22.     # 写入 CSV 文件
  23.     with open('data.csv', mode='w', newline='', encoding='utf-8') as file:
  24.         csvwriter = csv.writer(file)
  25.         csvwriter.writerow(['Name', 'Year', 'Country', 'Score'])  # 写入表头
  26.         for match in result:
  27.             dic = match.groupdict()
  28.             csvwriter.writerow([dic['name'].strip(), dic['year'].strip(), dic['country'].strip(), dic['score'].strip()])
  29. except Exception as e:
  30.     print(f"请求失败:{e}")
  31. finally:
  32.     print("程序结束!")
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4