9139 位艺人在 Python 面前不值一提

「这是我参与11月更文挑战的第14天,活动详情查看:2021最后一次更文挑战

本篇博客的最终目标是爬取世界上 9139 位艺人的身高、体重、生日、血型,当然有些数据目标网站没有提供,不在做过多的扩展。
9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9

爬取前的分析工作

目标网址为:www.ylq.com/star/list-a…,数据量在 9000+位艺人,单纯从数据上看量不是很大,合计 153 页数据。

页面未通过接口返回数据,查看页面源码即可看到。

9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9
列表页对应的数据如下图:
9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9
从图片中可以看到内页地址,头像图片,姓名这些直观数据。点击进入内页,对应的数据如下图所示。
9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9
整理爬取思路为,通过列表页面抓取所有的艺人内页,然后进入内页之后获取详细,将详细数据存档即可完成本案例。

编码时间

单线程爬取

本案例涉及的所有模块可以优先导入

1
2
3
4
5
6
7
8
python复制代码import requests
import re
import json
import threading
import csv
import codecs
import fcntl # 该模块未用到,可以暂时忽略
import time

对于页面的获取与解析相对比较简单,优先对这部分做出说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
python复制代码flag_page = 0
def get_list():
global flag_page
while flag_page < 154:
time.sleep(1)
flag_page += 1
print(f"正在爬取{flag_page}页数据")
url = f"http://www.ylq.com/star/list-all-------{flag_page}.html"
try:
r = requests.get(url=url, headers=headers)
pattern = re.compile(
r'<li>[.\s]*<a href="(.*?)" target="_blank">[.\s]*<img src="(.*?)" width="\d+" height="\d+" alt=".*?" /><span class="bg"></span><h2>(.*?)</h2>')
# 获取页面中的所有艺人
famous = pattern.findall(r.text)
print(famous)
except Exception as e:
print(e)

continue

if __name__ == "__main__":
get_list()

上述代码说明:

  • 本案例会利用到简单的多线程操作,所以提前声明一个全局变量 flag_page 方便后续使用
  • 正则表达式需要的反复练习,如果无法一次完整匹配,可以采用多次匹配的方式进行

优化成多线程模式

从现在的代码修改为多线程非常简单,只需要修改函数调用部分的代码即可。具体如下:

1
2
3
4
5
python复制代码if __name__ == "__main__":
for i in range(1, 6):
t = threading.Thread(target=get_list)
t.setName(f't{i}')
t.start()

循环创建 5 个线程,每个线程的名字设置为 tn,线程的初始化与使用代码如下:

1
2
python复制代码 t = threading.Thread(target=get_list) # 初始化
t.start() # 启动

此时代码会同时并发 5 个线程,速度会有极大的提高。

抓取内页

在每个线程获取到数据之后,就可以对内页进行分析了,从上面获取到的解析数据中提取出内页链接。

1
2
3
4
5
6
7
python复制代码# 获取页面中的所有艺人
famous = pattern.findall(r.text)
for user in famous:
# 内页地址
detail_url = user[0]
# print(detail_url)
data = get_detail(detail_url)

接下来扩展 get_detail 函数,该部分主要为正则表达式的应用,函数具体内容如下:

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
python复制代码def get_detail(detail_url):

r = requests.get(url=detail_url, headers=headers)
r.encoding = "utf-8"
html = r.text
# 截取字符串
start = html.find('<div class="sLeft">')
end = html.find('<div class="sRight">')
html = html[start:end]
# 获取姓名和职业
name_type = re.search(
r'<h1>(?P<name>.*?)<span>(?P<type>.*?)</span></h1>', html)
# 获取地区
city = re.search(r'<li><span>地区:</span>(?P<city>.*?)</li>', html)
high = re.search(
r'<li><span>身高:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<high>.*?)(</a>)?</li>', html)
weight = re.search(r'<li><span>体重:</span>(?P<weight>.*?)</li>', html)
birthday = re.search(r'<li><span>生日:</span>(?P<birthday>.*?)</li>', html)
star = re.search(
r'<li><span>星座:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<star>.*?)(</a>)?</li>', html)
blood = re.search(
r'<li><span>血型:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<blood>.*?)(</a>)?</li>', html)

detail = {
'name': name_type.group('name'),
'type': name_type.group('type'),
'city': city.group('city'),
'high': high.group('high'),
'weight': weight.group('weight'),
'birthday': birthday.group('birthday'),
'star': star.group('star'),
'blood': blood.group('blood')
}

return detail

上述内容会将匹配到的数据返回给主函数,代码运行之后,就可以打印出我们想要的信息了。
9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9
最后,只需要将数据存在本地的 csv 文件即可,完整代码参照:

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
python复制代码import requests,re,json,threading,csv,codecs,time

headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36"
}

flag_page = 0

def get_detail(detail_url):
r = requests.get(url=detail_url, headers=headers)
r.encoding = "utf-8"
html = r.text
# 截取字符串
start = html.find('<div class="sLeft">')
end = html.find('<div class="sRight">')
html = html[start:end]
# 获取姓名和职业
name_type = re.search(
r'<h1>(?P<name>.*?)<span>(?P<type>.*?)</span></h1>', html)

city = re.search(r'<li><span>地区:</span>(?P<city>.*?)</li>', html)
high = re.search(
r'<li><span>身高:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<high>.*?)(</a>)?</li>', html)
weight = re.search(r'<li><span>体重:</span>(?P<weight>.*?)</li>', html)
birthday = re.search(r'<li><span>生日:</span>(?P<birthday>.*?)</li>', html)
star = re.search(
r'<li><span>星座:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<star>.*?)(</a>)?</li>', html)
blood = re.search(
r'<li><span>血型:</span>(<a href="(.*?)" target="_blank" title="(.*?)">)?(?P<blood>.*?)(</a>)?</li>', html)

detail = {
'name': name_type.group('name'),
'type': name_type.group('type'),
'city': city.group('city'),
'high': high.group('high'),
'weight': weight.group('weight'),
'birthday': birthday.group('birthday'),
'star': star.group('star'),
'blood': blood.group('blood')
}

return detail

def save_face():
pass

def save(all_data):
# fcntl.flock(f.fileno(), fcntl.LOCK_EX) # 加锁
with open('users.csv', 'a+', newline='', encoding='utf-8-sig') as f:
fieldnames = {'name', 'type', 'city', "high",
'weight', 'birthday', 'star', 'blood'}
writer = csv.DictWriter(f, fieldnames=fieldnames)
for i in all_data:
writer.writerow(i)

def get_list():
global flag_page
# name = threading.currentThread().name
# print(f"当前线程名字为{name}")
while flag_page < 154:
time.sleep(1)
flag_page += 1
print(f"正在爬取{flag_page}页数据")
url = f"http://www.ylq.com/star/list-all-------{flag_page}.html"
try:
r = requests.get(url=url, headers=headers)
pattern = re.compile(
r'<li>[.\s]*<a href="(.*?)" target="_blank">[.\s]*<img src="(.*?)" width="\d+" height="\d+" alt=".*?" /><span class="bg"></span><h2>(.*?)</h2>')
famous = pattern.findall(r.text)
all_data = []
for user in famous:
detail_url = user[0]
# print(detail_url)
data = get_detail(detail_url)
all_data.append(data)

save(all_data)
except Exception as e:
print(e)
print(f"{detail_url}出现问题")
continue

if __name__ == "__main__":
with open('users.csv', 'w', newline='') as f:
fieldnames = {'name', 'type', 'city', "high",
'weight', 'birthday', 'star', 'blood'}
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
for i in range(1, 6):
t = threading.Thread(target=get_list)
t.setName(f't{i}')
t.start()

数据说明

数据爬取到本地为 500KB 整理量不大,存在很多不详,如果你想要该份数据用于分析,直接运行上述代码即可成功。
9139 位艺人在 Python 面前不值一提 Python 爬虫小课 5-9
代码中需要特别注意的是正则的泛应用,csv 文件的存储(带列头),简单多线程的应用。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%