crawlers基础

一、爬取基础

1
检查网页源代码的目的是:查看所要爬取的内容是由那个url加载的,之后的代码实现就是围绕这个url

1.简单网页抓取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from urllib.request import urlopen # urllib中的一个叫request模块中的urlopen函数

url = "https://www.baidu.com"
response = urlopen(url) # urlopen函数是访问这个网址,返回一个http.client.HTTPResponse对象



print(response.read().decode("utf-8")) # read()函数是读取响应体,返回字节类型的内容 # ctrl+f charset: 得知网页是由那个字符集编码的
# 字符解码之后,就可看到正常的html代码了

# 将html代码写入到test.html文件中

with open("./test.html", "w", encoding="utf-8") as f: # with: 上下文管理器 open()函数: 打开一个文件,"w"表示写入模式,encoding指定编码格式 as f: 表示将文件对象赋值给f
f.write(response.read().decode("utf-8"))


上述脚本是无法实现要求的:
HTTP响应体只能被读取一次。当你第一次调用response.read()后,响应内容已经被消耗了,第二次调用会返回空字节,所以文件写入的是空内容

也就是说,要使用变量存储一下网页内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from urllib.request import urlopen # urllib中的一个叫request模块中的urlopen函数

url = "https://www.baidu.com"
response = urlopen(url) # urlopen函数是访问这个网址,返回一个http.client.HTTPResponse对象

html_content = response.read().decode("utf-8")

print(html_content) # read()函数是读取响应体,返回字节类型的内容 # ctrl+f charset: 得知网页是由那个字符集编码的
# 字符解码之后,就可看到正常的html代码了

# 将html代码写入到test.html文件中

with open("./test.html", "w", encoding="utf-8") as f: # with: 上下文管理器 open()函数: 打开一个文件,"w"表示写入模式,encoding指定编码格式 as f: 表示将文件对象赋值给f
f.write(html_content) # write()函数: 将内容写入文件

# w 覆盖 a 追加


2.requests库-get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
一些基本框架

import requests

# 爬取百度的页面源代码

url = "http://www.baidu.com"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象

print(resp.status_code) # 获取状态码

resp.encoding = "utf-8" # 手动指定编码格式

print(resp.text) # 拿到页面源代码


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

# 爬取百度的页面源代码

query = input("请输入要搜索的内容: ")
url = f"https://www.sogou.com/web?query={query}"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求

print(resp.text) # 拿到页面源代码


问题出现在发送的请求头: user-agent
print(resp.request.headers)
{‘User-Agent’: ‘python-requests/2.32.5’, ‘Accept-Encoding’: ‘gzip, deflate’, ‘Accept’: ‘/‘, ‘Connection’: ‘keep-alive’, ‘Cookie’: ‘ABTEST=0|1769246131|v17; SNUID=F6B114304146085FF7752DA84149FB07; IPLOC=CN4412; SUID=B7F05571DBA6A20B0000000069748DB3; cuid=AAFCduelWAAAAAuippMXowEAEAM=’}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests

# 爬取百度的页面源代码

query = input("请输入要搜索的内容: ")
url = f"https://www.sogou.com/web?query={query}"

resp = requests.get(url) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求`

headers= {

"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0"

}

resp = requests.get(url, headers=headers) # get()函数: 发送一个GET请求,返回一个Response对象 这种简单的搜索几乎都是get请求`

print(resp.text) # 拿到页面源代码


3.requests库-post

此时的请求行:https://fanyi.baidu.com/sug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests

# 爬取百度的页面源代码

url = "https://fanyi.baidu.com/sug"

data = {

# "kw": "你好"
"kw": input("请输入要翻译的内容: ")

}

resp = requests.post(url, data=data) # 发送POST请求

# print(resp.text) # .text是拿到响应体的文本内容
print(resp.json()) # .json()是拿到响应体的JSON字符串,然后转换为Python字典

4.多get请求

  • 参数
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
# 多get参数

import requests

url = "https://movie.douban.com/j/chart/top_list?"

params = {
"type": 13,
"interval_id": "100:90",
"action": "",
"start": 0,
"limit": 20
}

headers = { # 模拟浏览器访问 绕过反爬
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}

resp = requests.get(url, params=params, headers=headers)

print(resp.request.url)
print(resp.status_code)
print(resp.text)
print(resp.json())

二、数据解析

1.python正则

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
. : 匹配除\n外任意单个字符  re模块的一个坑
\d : 匹配数字(0-9)
\D : 匹配非数字
\w : 匹配字母/数字/下划线
\W : 匹配非字母/数字/下划线
\s : 匹配空白字符(空格/\t/\n)
\S : 匹配非空白字符

--------------------------------------
* : 前字符匹配0+次(贪婪)
*? : 前字符匹配0+次(非贪婪)
+ : 前字符匹配1+次(贪婪)
+? : 前字符匹配1+次(非贪婪)
? : 前字符匹配0/1次(贪婪)
?? : 前字符匹配0/1次(非贪婪)
{n} : 前字符匹配n次
{n,} : 前字符匹配≥n次(贪婪)
{n,}? : 前字符匹配≥n次(非贪婪)
{n,m} : 前字符匹配n-m次(贪婪)
{n,m}? : 前字符匹配n-m次(非贪婪)


--------------------------------------
^ : 匹配字符串开头(re.M模式匹配每行开头)
$ : 匹配字符串结尾(re.M模式匹配每行结尾)
\b : 匹配单词边界
\B : 匹配非单词边界
() : 分组(可通过\num引用)
(?:) : 非捕获分组
| : 多选一匹配
\num : 反向引用第num个分组结果
\ : 转义元字符为字面量(如\.匹配点)
[] : 字符集合(匹配内任意字符)
[^] : 否定集合(匹配外任意字符)
  • 重要例子理解
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
这是对于一个匹配的字符所说的匹配多少次
比如 "我有100000元" \d* \d? \d+

字符序列:我(0)、有(1)、1(2)、0(3)、0(4)、0(5)、0(6)、0(7)、元(8)

\d* 允许匹配 0 次数字(非数字位置),遇到数字则贪婪匹配所有连续数字:
位置 0(字符我):非数字 → \d* 匹配 0 次数字 → 结果:空字符串'' 注意,他是有匹配的,只是匹配结果为空
位置 1(字符有):非数字 → \d* 匹配 0 次数字 → 结果:空字符串''
位置 2(字符1):是数字 → 贪婪向后匹配所有连续数字(位置 2-7:1、0、0、0、0、0)→ 结果:100000
位置 8(字符元):非数字 → \d* 匹配 0 次数字 → 结果:空字符串''


\d? 最多匹配 1 次数字,非数字位置匹配 0 次,数字位置仅匹配 1 个字符:
位置 0(字符我):非数字 → 匹配 0 次 → 结果:''
位置 1(字符有):非数字 → 匹配 0 次 → 结果:''
位置 2(字符1):是数字 → 匹配 1 次 → 结果:1
位置 3(字符0):是数字 → 匹配 1 次 → 结果:0
位置 4(字符0):是数字 → 匹配 1 次 → 结果:0
位置 5(字符0):是数字 → 匹配 1 次 → 结果:0
位置 6(字符0):是数字 → 匹配 1 次 → 结果:0
位置 7(字符0):是数字 → 匹配 1 次 → 结果:0
位置 8(字符元):非数字 → 匹配 0 次 → 结果:''


\d+ 要求至少匹配 1 次数字,非数字位置直接跳过,遇到数字则贪婪匹配所有连续数字:
位置 0(字符我):非数字 → 不满足 “至少 1 次” → 无匹配结果
位置 1(字符有):非数字 → 不满足 “至少 1 次” → 无匹配结果
位置 2(字符1):是数字 → 贪婪向后匹配所有连续数字(位置 2-7:100000)→ 结果:100000
位置 8(字符元):非数字 → 不满足 “至少 1 次” → 无匹配结果





玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏 玩.*游戏

字符序列:玩(0)、吃(1)、鸡(2)、游(3)、戏(4)、,(5)、干(6)、嘛(7)、呢(8)、?(9)、上(10)、机(11)、玩(12)、游(13)、戏(14)、,(15)、玩(16)、游(17)、戏(18)

正则首先匹配第一个玩(位置 0),触发匹配起始;
.* 进入贪婪模式,从位置 1 开始向后尽可能多匹配所有字符,直到找到最后一个能匹配游戏的位置:
先遇到位置 3-4 的 “游戏”,但*贪婪,不会停,继续向后匹配;
再遇到位置 13-14 的 “游戏”,仍不停,继续匹配到位置 17-18 的 “游戏”(最后一个);
匹配终止,最终匹配范围是:位置 0 → 位置 18(即从第一个 “玩” 到最后一个 “游戏” 的全部内容)。

结果:['玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏']


玩吃鸡游戏,干嘛呢?上机玩游戏,玩游戏 玩.*?游戏

第一步:匹配第一个 “玩吃鸡游戏”
正则先匹配第一个 “玩”(位置 0),触发匹配起始;
.*? 开始非贪婪匹配中间字符,从位置 1 开始向后找,直到遇到第一个 “游戏”:
位置 1(吃)→ 位置 2(鸡)→ 位置 3(游)→ 位置 4(戏):遇到 “游戏”(位置 3-4),.*? 停止匹配(仅匹配了 “吃鸡”);
匹配终止,第一个结果:玩吃鸡游戏(位置 0-4)。

第二步:匹配第二个 “玩游戏”
正则从第一步结束的位置(位置 5)继续向后找下一个 “玩”,直到位置 12(字符 “玩”),触发第二次匹配起始;
.*? 非贪婪匹配中间字符,从位置 13 开始:
位置 13(游)→ 位置 14(戏):直接遇到 “游戏”,.*? 匹配 0 个中间字符;
匹配终止,第二个结果:玩游戏(位置 12-14)。

第三步:匹配第三个 “玩游戏”
正则从第二步结束的位置(位置 15)继续向后找下一个 “玩”,直到位置 16(字符 “玩”),触发第三次匹配起始;
.*? 非贪婪匹配中间字符,从位置 17 开始:
位置 17(游)→ 位置 18(戏):直接遇到 “游戏”,.*? 匹配 0 个中间字符;
匹配终止,第三个结果:玩游戏(位置 16-18)。

['玩吃鸡游戏', '玩游戏', '玩游戏'] 事实上这个机制是现贪婪找到最后一个游戏,再往回溯



2.re库的使用

  • 基础函数
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
import re


result1 = re.findall(r"\d+", "123a456b789") # 查找所有符合正则表达式的字符串 参数1: 正则表达式 参数2: 要匹配的字符串
# 加 r 表示原始字符串 避免转义字符的问题
print(result1)
print("".join(result1)) # 将列表转换为字符串

result2 = re.finditer(r"\d+", "我今年18岁,有10000000块") # finditer 返回的是匹配对象的迭代器
for i in result2: # nums 这里是迭代器数组
print(i.group()) # 打印匹配到的字符串

result3 = re.search(r"\d+", "我叫周杰伦,今年18岁,是五年级4班学生") # search 返回的是匹配到的第一个字符串
print(result3.group()) # 打印匹配到的字符串



result4 = re.match(r"\d+", "我叫周杰伦,今年18岁,是五年级4班学生") # match 是在字符串的开头进行匹配,类似在真正表达式的开头加上 ^
# 即默认是 ^\d+ 很少用
print(result4.group()) # 打印匹配到的字符串


# 预加载正则表达式
# 作用:将正则表达式编译为正则表达式对象
# 编译后的正则表达式对象可以重复使用,避免重复编译

pattern = re.compile(r"\d+")



  • 例子
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
import re


content = """

<div class='西游记'><span id='10010'>中国联通</span></div>
<div class='红楼梦'><span id='10086'>中国移动</span></div>
<div class='水浒传'><span id='10001'>中国电信</span></div>
<div class='三国演义'><span id='10000'>广电</span></div>

"""

pattern_1 = re.compile(r"<div class='.*'><span id='\d*'>.*?</span></div>")

result = pattern_1.findall(content)

for item in result:
print(item)


print("-"*50)

# 提取数据就需要用到()分组

pattern_2 = re.compile(r"<div class='(.*)'><span id='(\d*)'>(.*?)</span></div>")

result = pattern_2.findall(content)

for item in result:
print(item)


print("-"*50)

pattern_3 = re.compile(r"<div class='(.*)'><span id='(?P<id>\d*)'>(?P<name>.*?)</span></div>")
# 没有设置字段名的会被忽略

result = pattern_3.finditer(content)

for item in result:
print(item.groupdict())
print(item.group("id"), item.group("name"))



<div class='西游记'><span id='10010'>中国联通</span></div>
<div class='红楼梦'><span id='10086'>中国移动</span></div>
<div class='水浒传'><span id='10001'>中国电信</span></div>
<div class='三国演义'><span id='10000'>广电</span></div>
--------------------------------------------------
('西游记', '10010', '中国联通')
('红楼梦', '10086', '中国移动')
('水浒传', '10001', '中国电信')
('三国演义', '10000', '广电')
--------------------------------------------------
{'id': '10010', 'name': '中国联通'}
10010 中国联通
{'id': '10086', 'name': '中国移动'}
10086 中国移动
{'id': '10001', 'name': '中国电信'}
10001 中国电信
{'id': '10000', 'name': '广电'}
10000 广电


3.bs4解析

1
2
3
4
bs4是 Python 专门用于解析 HTML/XML 文档的第三方库,可以把它理解成一个 “网页内容整理工”:
网页源代码本质是一堆杂乱的字符串(比如你之前爬取的电影页面源码),直接用正则提取内容需要写复杂的匹配规则,还容易出错;
bs4能把这堆字符串转换成结构化、可操作的对象,像 “查字典” 一样
通过标签名(如<div>、<a>)、类名(class)、属性(href)等简单规则,精准提取想要的内容,不用再写复杂的正则。
  • bs4_base
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
from bs4 import BeautifulSoup # 从bs4库导入BeautifulSoup类

simple_html = """
<ul>
<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
<li><a href="zhubajie.com">猪八戒</a></li>
<li><a href="wuzetian.com">武则天</a></li>
</ul>
"""

# 创建BeautifulSoup对象(核心:解析HTML)
# "html.parser"是Python内置的解析器,无需额外安装
soup = BeautifulSoup(simple_html, "html.parser")

# find("标签名") 第一个符合条件的标签
# find("标签名", attrs={"属性名": "属性值"}) 查找对应值的标签
# find_all() 方法: 查找所有符合条件的标签
# 嵌套的标签可以逐层提取
# 例如:先找到<ul>,再在<ul>中找<li>

li_1 = soup.find("li")
print(li_1)

li_2 = soup.find("li", attrs={"id": "abc"})
print(li_2)

print("-" * 20)

ul = soup.find("ul")
print(ul)

li_list = ul.find_all("li")

for li in li_list:
a = li.find("a")
print(a)
print(a["href"]) # 或 a.get("href")
print(a.text)


# 拿文本 .text 或 .get_text()
# 拿属性 ["属性名"] 或 .get("属性名")


<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
--------------------
<ul>
<li><a href="zhangwuji.com">张无忌</a></li>
<li id="abc"><a href="zhouxingchi.com">周星驰</a></li>
<li><a href="zhubajie.com">猪八戒</a></li>
<li><a href="wuzetian.com">武则天</a></li>
</ul>
<a href="zhangwuji.com">张无忌</a>
zhangwuji.com
张无忌
<a href="zhouxingchi.com">周星驰</a>
zhouxingchi.com
周星驰
<a href="zhubajie.com">猪八戒</a>
zhubajie.com
猪八戒
<a href="wuzetian.com">武则天</a>
wuzetian.com
武则天


4.xpath解析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
XPath 是什么?
1.html 是 xml 的子集
2.把 HTML/XML 文档想象成一棵「标签树」(比如<html>是根节点,<div>是分支,<a>是叶子),XPath 就是这棵树的「导航路径」——
3.通过写 XPath 表达式,能精准定位到你想要的标签 / 属性 / 文本就像用地址找快递:中国→北京市→丰台区→XX小区→1号楼→301室 一样,XPath 就是给 HTML 标签写 “地址"


//:从文档任意位置找(忽略层级);
/:子节点(必须是直接子级);
@:匹配属性(比如@class、@id、@href);
text():提取标签内的文本;
[]:条件筛选(比如[@class='xxx']、[1])



  • xml
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
from lxml import etree


xml = '''

<book>
<id>1</id>
<name>野花遍地香</name>
<name>野花遍地不香</name>
<price>1.23</price>
<nick>臭豆腐</nick>
<author>
<nick id="10086">周大强</nick>
<nick id="10010">周芷若</nick>
<nick class="joy">周杰伦</nick>
<nick class="jolin">蔡依林</nick>
<div>
<nick>惹了</nick>
</div>
</author>
<partner>
<nick id="ppc">胖胖陈</nick>
<nick id="ppbc">胖胖不陈</nick>
</partner>
</book>

'''

# 有html() xml()
et = etree.XML(xml)
book = et.xpath("/book") # / 表示根节点
print(book)
name = et.xpath("/book/name")
print(name)
name_text = et.xpath("/book/name/text()") # text() 表示获取文本内容, 返回的是一个列表
print(name_text)
print(name_text[0])
nick_list1 = et.xpath("/book//nick") # /book//nick 表示获取所有子孙节点中所有的nick标签
print(nick_list1)
nick_list2 = et.xpath("/book/*/nick") # /book/*/nick 表示获取所有直接子节点中所有的nick标签
print(nick_list2)

jay = et.xpath("/book/author/nick[@class='joy']/text()") # @class='joy' 表示获取class属性值为joy的nick标签 返回的依然是一个列表
print(jay[0])

ids = et.xpath("/book/partner/nick/@id") # @id 表示获取id属性值 返回的依然是一个列表
for id in ids:
print(id)


  • 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
from lxml import etree


html = '''

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉嘉</div>
<div class="common">胡辣汤</div>
</body>
</html>


'''

et = etree.HTML(html)

google = et.xpath("/html/body/ul/li[2]/a/@href") # 其实位置是1
print(google[0])


# 就找li

li_list = et.xpath("//li")

for li in li_list:
href = li.xpath("./a/@href") # 列表
text = li.xpath("./a/text()") # 列表
print(href, text)



http://www.google.com
['http://www.baidu.com'] ['百度']
['http://www.google.com'] ['谷歌']
['http://www.sogou.com'] ['搜狗']
['feiji'] ['飞机']
['dapao'] ['大炮']
['huoche'] ['火车']

  • 在大量的网页代码确定xpath路径

实战1:爬取电影top250

  • 爬取模板
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
<li>
<div class="item">
<div class="pic">
<em>1</em>
<a href="https://movie.douban.com/subject/1292052/">
<img width="100" alt="肖申克的救赎" src="https://img3.doubanio.com/view/photo/s_ratio_poster/public/p480747492.webp">
</a>
</div>
<div class="info">
<div class="hd">
<a href="https://movie.douban.com/subject/1292052/">
<span class="title">肖申克的救赎</span>
<span class="title">&nbsp;/&nbsp;The Shawshank Redemption</span>
<span class="other">&nbsp;/&nbsp;月黑高飞(港) / 刺激1995(台)</span>
</a>


<span class="playable">[可播放]</span>
</div>
<div class="bd">
<p>
导演: 弗兰克·德拉邦特 Frank Darabont&nbsp;&nbsp;&nbsp;主演: 蒂姆·罗宾斯 Tim Robbins /...<br>
1994&nbsp;/&nbsp;美国&nbsp;/&nbsp;犯罪 剧情
</p>


<div>
<span class="rating5-t"></span>
<span class="rating_num" property="v:average">9.7</span>
<span property="v:best" content="10.0"></span>
<span>3251199人评价</span>
</div>

<p class="quote">
<span>希望让人自由。</span>
</p>


<p>

<span class="gact">
<a href="https://movie.douban.com/wish/293299521/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-wish" rel="nofollow">想看</a>
</span>&nbsp;&nbsp;

<span class="gact">
<a href="https://movie.douban.com/collection/293299521/update?add=1292052" target="_blank" class="j a_collect_btn" name="sbtn-1292052-collection" rel="nofollow">看过</a>
</span>&nbsp;&nbsp;
</p>

</div>
</div>
</div>
</li>
  • cookie获取
  • 页面跳转
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import requests
import re
import csv # 用于CSV写入
import json # 用于json写入
import random # 用于随机延时
import time # 用于延时


base_url = "https://movie.douban.com/top250"

# pattern = re.compile(
# r'''
# <li>.*?
# <span class="title">(?P<name>.*?)</span>
# .*?<p>.*?导演:(?P<director>.*?)
# .*?&nbsp;(?P<actor>.*?)
# .*?<br>(?P<year>.*?)
# .*?&nbsp;(?P<contry>.*?)
# .*?&nbsp;(?P<type>.*?)
# .*?(?P<score>\d+)</span>
# .*?(?P<persons>\d)人评价
# </li>
# '''
# , re.S) # 我的错误

pattern = re.compile(
r'<li>.*?'
# 电影名称:匹配到下一个<span class="title">(副标题)或</a>截止
r'<span class="title">(?P<name>[^/]+?)</span>.*?'
# 导演+主演:匹配到<p>闭合标签前的<br>截止
r'导演:\s*(?P<director>[^&]+?)\s*&nbsp;&nbsp;&nbsp;主演:\s*(?P<actor>[^<]+?)\s*<br>.*?'
# 年份/国家/类型:匹配到</p>截止(避免包含后面的<div>)
r'(?P<year>\d{4})\s*&nbsp;/&nbsp;\s*(?P<country>[^&]+?)\s*&nbsp;/&nbsp;\s*(?P<type>[^<]+?)\s*</p>.*?'
# 评分:精准匹配rating_num标签内的内容
r'<span class="rating_num" property="v:average">(?P<score>\d+\.\d+)</span>.*?'
# 评价人数:匹配到“人评价”前的数字,后面跟</span>截止
r'(?P<persons>\d+)人评价</span>'
, re.S | re.M)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"Referer": "https://www.douban.com/", # 增加来源页,模拟正常访问
"Cookie": 'll="118290"; bid=P5S04iWSpqg; _vwo_uuid_v2=DC66DCF2BF77499C0C67FB201BB813A47|989d543c015801abda54a5fb298d38ec; ap_v=0,6.0; dbcl2="293299521:v1DkVrRnkvM"; ck=-2P0; push_noty_num=0; push_doumail_num=0; frodotk_db="64215c40a36a96dc2c6414c1ea3fc494"'
}


movie_list = [] # 用于存储所有电影信息的列表
movie_rank = 1 # 用于记录当前电影的排名

try:

for start in range(0, 226, 25):

page_num = start // 25 + 1
url = f"{base_url}?start={start}&filter="
time.sleep(random.uniform(1, 2))
result = requests.get(url, headers=headers)
result.encoding = 'utf-8'
result = pattern.finditer(result.text)

for item in result:
movie_list.append({
"rank": movie_rank,
"name": item.group("name"),
"director": item.group("director"),
"actor": item.group("actor"),
"year": item.group("year"),
"country": item.group("country"),
"type": item.group("type"),
"score": item.group("score"),
"persons": item.group("persons")
})
movie_rank += 1

if movie_list:
print("成功收集到电影信息")

with open("doubao_top250.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.DictWriter(f, fieldnames=movie_list[0].keys())
writer.writeheader()
writer.writerows(movie_list)

print("电影信息已写入doubao_top250.csv")

else:
print("未收集到电影信息")


except Exception as e:
print(e)


实战2:爬取dy2018热片

  • 任务

爬取2026年热片信息,以及其下载地址

  • 很细节
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
74
75
76
77
import requests
import re
import csv

# 拿到2026必看热片的url 凭借存入数组中
# 逐个访问数组中的url,进入子页面获取其中的电影信息

base_url = "https://www.dytt8899.com/"

resp = requests.get(base_url)
resp.encoding = "gbk"

pattern_1 = re.compile(r"2026必看热片.*?<ul>(?P<html>.*?)</ul>", re.S)

result_1 = pattern_1.search(resp.text)

html = result_1.group("html")

# 仅保留href分组的正则
pattern_2 = re.compile(
r"<li><a href='(?P<href>.*?)' title=",
re.S # 开启单行模式,让.匹配换行符
)

result_2 = pattern_2.finditer(html)

movies_list = []

for item in result_2:
movies_list.append(base_url.strip("/") + item.group("href"))

for i in range(len(movies_list)): # 默认从0开始到len(movies_list)-1
print(movies_list[i])

# 访问子页面获取电影信息
movies_info = []

pattern_4 = re.compile(
r'<div id="Zoom">.*?◎片  名 (?P<name>.*?)<br />◎年  代 (?P<year>.*?)<br />.*?'
r'<td style="WORD-WRAP: break-word" bgcolor="#fdfddf"><a href="(?P<magnet>.*?)">',
re.S
)

for url in movies_list:
resp = requests.get(url)
resp.encoding = "gbk"

result_4 = pattern_4.search(resp.text)

name = result_4.group("name")
year = result_4.group("year")
magnet = result_4.group("magnet")
movies_info.append({
"name": name,
"year": year,
"magnet": magnet
})

# 打印所有电影信息
for item in movies_info:
print(item)

# 1. 定义CSV文件路径和列名
csv_file = "./dy2018.csv"
# 列名要和字典的key对应
headers = ["name", "year", "magnet"]

# 2. 写入CSV(newline=''避免空行,encoding='utf-8-sig'避免中文乱码)
with open(csv_file, "w", newline="", encoding="utf-8-sig") as f:
# 创建CSV写入器
writer = csv.DictWriter(f, fieldnames=headers)
# 写入表头
writer.writeheader()
# 写入所有电影数据
writer.writerows(movies_info)

print(f"\n✅ 数据已成功写入 {csv_file} 文件!")

实战3:爬取新发地菜品单价

  • 请求方式
  • 参数
  • 返回数据的格式内容
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

# 单线程

import requests
import csv
import time

url = "http://www.xinfadi.com.cn/getPriceData.html"

# 请求头
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded"
}

# 基础请求参数
base_data = {
"limit": "20",
"current": "1", # 初始页码是1
"pubDateStartTime": "2024/01/01",
"pubDateEndTime": "",
"prodPcatid": "",
"prodCatid": "",
"prodName": ""
}


# 先请求第1页,拿到总条数count
response = requests.post(url, headers=headers, data=base_data)
result = response.json() # 返回的是一个字典,包含count和list两个键
total_count = result["count"] # 总数据条数(比如页面显示的“共345962条”)
page_size = int(base_data["limit"]) # 每页20条
total_pages = (total_count // page_size) + 1 # 总页数(345962÷20≈17298页)
print(f"发现共{total_count}条数据,分{total_pages}页")


all_data = [] # 存所有页面的数据

for page in range(1, total_pages + 1):
try:
# 更新参数:当前页码
base_data["current"] = str(page)

# 发送请求
response = requests.post(url, headers=headers, data=base_data)
response.raise_for_status()
page_result = response.json()
page_data = page_result["list"] # 当前页的价格数据

# 把当前页数据加到总列表
all_data.extend(page_data)

# 打印进度
print(f"已爬第{page}/{total_pages}页,累计{len(all_data)}条数据")

# 加延时(每爬1页停0.5秒,避免给服务器压力)
time.sleep(0.5)

except Exception as e:
print(f"第{page}页爬取失败:{e}")
continue # 失败就跳过这一页,继续爬下一页

csv_headers = [
"一级分类", "二级分类", "品名", "最低价", "平均价",
"最高价", "规格", "产地", "单位", "发布日期"
]

# 打开CSV文件,写入数据
with open("xinfadi.csv", "w", newline="", encoding="utf-8-sig") as f:
writer = csv.DictWriter(f, fieldnames=csv_headers)
writer.writeheader() # 写入列名

# 循环每条数据,按列名对应字段
for item in all_data:
writer.writerow({
"一级分类": item.get("prodCat", ""),
"二级分类": item.get("prodPcat", ""),
"品名": item.get("prodName", ""),
"最低价": item.get("lowPrice", ""),
"平均价": item.get("avgPrice", ""),
"最高价": item.get("highPrice", ""),
"规格": item.get("specInfo", ""),
"产地": item.get("place", ""),
"单位": item.get("unitInfo", ""),
"发布日期": item.get("pubDate", "")[:10]
})

print(f"全部数据已写入CSV!共{len(all_data)}条")