Python爬虫

[toc]

request库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
r = requests.get("https://www.icourse163.org")
print(r.status_code) #状态码200,访问成功

print(type(r)) #<class 'requests.models.Response'>

#编码方式
print(r.encoding) #charset字段获得,不存在默认为ISO-8859-1
print(r.apparent_encoding) #备选编码,通过分析内容推断出编码

r.encoding = 'utf-8' #设置编码

print(r.text) #html内容
print(r.headers) #http请求头
print(r.content) #用于解析图片等内容

request对象属性

r.status_code HTTP请求的返回状态,200表示连接成功,404表示失败
r.text HTTP响应内容的字符串形式,即url对应的页面内容
r.encoding 从HTTP header中猜测的响应内容编码方式
r.apparent_encoding 从内容中分析出的响应内容编码方式(备选编码方式)
r.content HTTP响应内容的二进制形式

request对象方法

方法说明

requests.request() 构造一个请求,支撑以下各方法的基础方法
requests.get() 获取HTML网页的主要方法,对应于HTTP的GET
requests.head() 获取HTML网页头信息的方法,对应于HTTP的HEAD
requests.post() 向HTML网页提交POST请求的方法,对应于HTTP的POST
requests.put() 向HTML网页提交PUT请求的方法,对应于HTTP的PUT
requests.patch() 向HTML网页提交局部修改请求,对应于HTTP的PATCH
requests.delete() 向HTML页面提交删除请求,对应于HTTP的DELETE

通用代码框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import requests  
import time
def getHTMLText(url):
"""
获取指定 URL 的 HTML 内容

Parameters:
url (str): 要获取 HTML 内容的 URL

Returns:
str: 成功获取到的 HTML 内容,如果请求失败则返回 "产生异常"
"""
try:
r = requests.get(url,timeout=1) # 发送 HTTP GET 请求,可以通过timeout=1设置超时时间为 1 秒
r.raise_for_status() # 检查是否出现 HTTP 请求错误
r.encoding = r.apparent_encoding # 根据响应内容自动设置编码
return r.text # 返回 HTML 内容
except:
return "产生异常"

if __name__ == "__main__":

url1 = "https://www.baidu.com/"
print(getHTMLText(url1)) # 成功输出HTML内容
url2 = "https://chat.openai.com/"
print(getHTMLText(url2)) # 产生异常
first_time = time.time()
N = 100
for i in range(0,N):
getHTMLText(url1)
last_time = time.time()
print(f'访问{N}次耗时:{last_time-first_time}') #访问100次耗时:9.285466194152832
#可以看到request库性能较低

Robots协议

我们可以在网站的根目录建立一个robots.txt文件来说明限制爬虫的行为,但是这只是一种公示,不能真正阻止爬虫。

实例

  1. 当网站对访问用户设备有限制时,我们可以修改user-agent字段实现访问。
1
2
3
4
kv = {'user-agent':'Mozilla/5.0'}

r = requests.get(url,timeout=1,headers=kv)

2.部分网站提供API URL 搜索

百度:http://www.baidu.com/s?wd

360:http://www.so.com/s?q

百度的我没有爬到,这里发下360的爬虫

1
2
3
4
5
6
7
8
9
10
11
12
import requests
url ='http://www.so.com/s'
keyword ='Python'
kv = {'q':keyword}
try:
kv = {'q':keyword}
r= requests.get(url,params = kv)
r.raise_for_status()
r.encoding= r.apparent_encoding
print(r.text)
except:
print('爬取失败')
  1. 存储
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import requests  
import os

url ='https://img-blog.csdnimg.cn/f35b24a8c05744dca042a64ef2154c12.png'
root = "H:/pachong/" # 图片保存的根目录
path = root + url.split('/')[-1] # 图片保存的完整路径,使用URL中最后一个/后的文件名

try:
if not os.path.exists(root): # 如果根目录不存在,则创建
os.makedirs(root)
if not os.path.exists(path): # 如果文件不存在,则执行以下操作
r = requests.get(url)
with open(path,'wb') as f: # 以二进制写入模式打开文件
f.write(r.content) # 二进制写入
f.close()
print("文件已保存")
else:
print("文件已存在")
except:
print('爬取失败')

  1. ip归属地查询

我这里用的是ip38查询

1
2
3
4
5
6
7
8
9
10
11
12
13
import requests
url ='https://ip38.com/ip.php'
keyword ='ip-address'
kv = {'ip':keyword}
try:
kv = {'ip':keyword}
r= requests.get(url,params = kv)
r.raise_for_status()
r.encoding= r.apparent_encoding
print(r.request.url)
print(r.text)
except:
print('爬取失败')

Beautiful Soup

  • 默认编码为UTF-8

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
from bs4 import BeautifulSoup
import requests

url = 'https://baike.baidu.com/item/%E5%88%9D%E9%9F%B3%E6%9C%AA%E6%9D%A5/8231955'

try:
r = requests.get(url)
demo = r.text
soup = BeautifulSoup(demo,"html.parser") #html解析
print(soup.prettify()) #添加缩进和换行符,使得文档的结构更加清晰易读。
except:
print("error")

基本元素

基本元素说明

Tag 标签,最基本的信息组织单元,分别用<>和标明开头和结尾
Name 标签的名字,

的名字是’p’,格式:.name
Attributes 标签的属性,字典形式组织,格式:.attrs
NavigableString 标签内非属性字符串,<>…中字符串,格式:.string
Comment 标签内字符串的注释部分,一种特殊的Comment类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from bs4 import BeautifulSoup
import requests

url = 'https://python123.io/ws/demo.html'

try:
r = requests.get(url)
demo = r.text
soup = BeautifulSoup(demo,"html.parser")
#print(soup.prettify())
print(soup.title)

tag = soup.a
print(tag)
print(tag.name)
print(tag.parent.name)
print(tag.parent.parent.name)
print(tag.attrs)
print(tag.attrs['href'])
print(type(tag))

tag1 = soup.p
print(soup.p.string)
print(type(soup.p.string))


print(soup.prettify())

newsoup = BeautifulSoup("<b><!--This is a comment--></b><p>This is not a comment</p>","html.parser")
print(newsoup.b.string)
print(type(newsoup.b.string))
print(newsoup.p.string)
print(type(newsoup.p.string))
# 注意通过`.string`输出不会指出该类型为`comment`,所以我们要通过`type()`来判断是否为注释
except:
print("error")

HTML内容遍历

分为下行遍历上行遍历平行遍历,直接看代码更容易理解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from bs4 import BeautifulSoup  # 导入BeautifulSoup库,用于解析HTML
import requests # 导入requests库,用于发送HTTP请求

url = 'https://python123.io/ws/demo.html' # 目标页面URL

try:
r = requests.get(url) # 发送GET请求获取页面内容
demo = r.text # 获取页面文本
soup = BeautifulSoup(demo, "html.parser") # 解析页面

# 打印<head>标签的子节点
print(soup.head.contents)

# 打印<body>标签的子节点
print(soup.body.contents)

print("--------------------------------------------------------------------下行遍历---------------------------------------------------------------")

# 使用.children属性遍历<body>标签的子节点
for child in soup.body.children:
print(child)

# 打印<body>标签的子节点数量
print(len(soup.body.contents))
# 打印<body>标签的每个子节点
for i in range(len(soup.body.contents)):
print(soup.body.contents[i])

print("--------------------------------------------------------------------------------------------------------------------------------------------")

print("--------------------------------------------------------------------上行遍历---------------------------------------------------------------")

# 遍历<a>标签的所有父节点
for parent in soup.a.parents:
if parent is None:
print(parent)
else:
# 打印父节点的名称
print(parent.name)

print("--------------------------------------------------------------------------------------------------------------------------------------------")

print("--------------------------------------------------------------------平行遍历---------------------------------------------------------------")

# 打印<p>标签的文本内容
print(soup.p)
# 遍历<p>标签之后的所有兄弟节点
for sibling in soup.p.next_siblings:
print(sibling)

print("#########################################################################################################################################")

# 遍历<p>标签之前的所有兄弟节点
for sibling in soup.p.previous_siblings:
print(sibling)

print("--------------------------------------------------------------------------------------------------------------------------------------------")

except Exception as e:
# 捕获异常并打印错误信息
print("error:", e)

信息组织与提取

信息标记的三种方式

XML (eXtensible Markup Language)

  • 最早的通用信息标记语言,可扩展性好,但繁琐
  • Internet上的信息交互与传递
1
2
3
4
5
6
7
8
9
10
<person>
<firstName>Tian</firstName>
<lastName>Song</lastName>
<address>
<streetAddr>中关村南大街5号</streetAddr>
<city>北京市</city>
<zipcode>100081</zipcode>
</address>
<prof>Computer System</prof><prof>Security</prof>
</person>

JSON (JavaScript Object Notation)

  • 信息有类型,适合程序处理(js),较XML简洁

  • 移动应用云端和节点的信息通信,无注释

1
2
3
4
5
6
7
8
9
10
{
“firstName” : “Tian” ,
“lastName” : “Song” ,
“address” : {
“streetAddr” : “中关村南大街5号” ,
“city” : “北京市” ,
“zipcode” :100081
} ,
“prof” : [ “Computer System” , “Security” ]

YAML (YAML Ain't Markup Language)

  • 信息无类型,文本信息比例最高,可读性好

  • 各类系统的配置文件,有注释易读

1
2
3
4
5
6
7
8
9
firstName : Tian
lastName : Song
address :
streetAddr : 中关村南大街5号
city : 北京市
zipcode : 100081
prof :
‐Computer System
‐Security

信息提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from bs4 import BeautifulSoup  
import requests
import re

url = 'https://python123.io/ws/demo.html'

try:
r = requests.get(url) # 发送GET请求获取页面内容
demo = r.text # 获取页面文本
soup = BeautifulSoup(demo, "html.parser") # 使用BeautifulSoup解析页面

# 查找所有<a>标签并打印其href属性值
print("-----------------")
for link in soup.find_all('a'):
print(link.get('href'))
print("-----------------")
print()

# 查找所有标签名,并打印出每个标签的名称
print("-----------------")
for tag in soup.find_all(True):
print(tag.name)
print("-----------------")
print()

# 使用正则表达式查找标签名中含有'b'的所有标签,并打印出标签名
print("-----------------")
for tag in soup.find_all(re.compile('b')):
print(tag.name)
print("-----------------")
print()

# 根据id属性值查找标签,并打印出符合条件的标签
print("-----------------")
for tag in soup.find_all(id='link1'):
print(tag)
print("-----------------")
for tag in soup.find_all(id=re.compile('link')):
print(tag)
print("-----------------")
print()

# 查找所有<a>标签及其子孙节点,并打印出每个标签
print("-----------------")
print(soup.find_all('a'))
print("-----------------")

# 只在当前层级查找<a>标签,并打印出每个标签
print(soup.find_all('a', recursive=False))
print("-----------------")
print()

# 根据文本内容查找标签,并打印出符合条件的标签
print("-----------------")
print(soup.find_all(string="Basic Python"))
print("-----------------")
print(soup.find_all(string=re.compile('Python')))
print("-----------------")

except Exception as e:
# 捕获异常并打印错误信息
print("error:", e)

拓展方法

方法说明

  • <>.find() 搜索且只返回一个结果,同.find_all()参数
  • <>.find_parents() 在先辈节点中搜索,返回列表类型,同.find_all()参数
  • <>.find_parent() 在先辈节点中返回一个结果,同.find()参数
  • <>.find_next_siblings() 在后续平行节点中搜索,返回列表类型,同.find_all()参数
  • <>.find_next_sibling() 在后续平行节点中返回一个结果,同.find()参数
  • <>.find_previous_siblings() 在前序平行节点中搜索,返回列表类型,同.find_all()参数
  • <>.find_previous_sibling() 在前序平行节点中返回一个结果,同.find()参数

中国大学排名定向爬虫实例

这里使用的是2023版的排名,8年过去网页结构已发生变化,稍加修改还能继续爬取【软科排名】2023年最新软科中国大学排名|中国最好大学排名 (shanghairanking.cn)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
from bs4 import BeautifulSoup
import bs4
import requests


def getHTMLText(url):
"""
获取指定URL的HTML文本内容

参数:
url (str): 目标网页的URL

返回:
str: 网页的HTML内容,如果请求失败则返回空字符串
"""
try:
r = requests.get(url, timeout=30)
r.raise_for_status()
r.encoding = r.apparent_encoding
return r.text
except:
return ""


def fillUnivList(ulist, html):
"""
将HTML文本中的大学信息填充到列表中

参数:
ulist (list): 存储大学信息的列表
html (str): 网页的HTML内容
"""
soup = BeautifulSoup(html, "html.parser")
for tr in soup.find('tbody').children:
if isinstance(tr, bs4.element.Tag): #是否为标签元素
tds = tr('td')
a = tds[0].string.strip()
b = tds[1].find('a', class_='name-cn').string.strip() #大学名
c = tds[1].find('p').string.strip() #985还211还是双一流
d = tds[2].text.strip() #地域,不能用stirng,返回了none,这里用text
e = tds[3].text.strip() #类型
f = tds[4].string.strip() #总分,text和string类型都可以
ulist.append([a, b, c, d, e, f])


def printUnivList(ulist, num):
"""
打印大学信息列表

参数:
ulist (list): 存储大学信息的列表
num (int): 打印的大学数量
"""
tplt = "{:^20}\t{:^20}\t{:^20}\t{:^20}\t{:^10}\t{:^10}"
print(tplt.format("排名", "学校名称", "级别", "地域", "类型", "总分"))
for i in range(num):
u = ulist[i]
print(tplt.format(u[0], u[1], u[2], u[3], u[4], u[5]))


def main():
"""
主函数,用于执行爬虫程序
"""
uinfo = []
url = 'https://www.shanghairanking.cn/rankings/bcur/202311'
html = getHTMLText(url)
fillUnivList(uinfo, html)
printUnivList(uinfo, 30)


main()