9139 位艺人在 Python 面前不值一提 # Python 爬虫小课 5-9

661 阅读4分钟

「这是我参与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 整理爬取思路为,通过列表页面抓取所有的艺人内页,然后进入内页之后获取详细,将详细数据存档即可完成本案例。

编码时间

单线程爬取

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

import requests
import re
import json
import threading
import csv
import codecs
import fcntl # 该模块未用到,可以暂时忽略
import time

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

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 方便后续使用
  • 正则表达式需要的反复练习,如果无法一次完整匹配,可以采用多次匹配的方式进行

优化成多线程模式

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

if __name__ == "__main__":
     for i in range(1, 6):
        t = threading.Thread(target=get_list)
        t.setName(f't{i}')
        t.start()

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

 t = threading.Thread(target=get_list) # 初始化
 t.start() # 启动

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

抓取内页

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

# 获取页面中的所有艺人
            famous = pattern.findall(r.text)
            for user in famous:
                # 内页地址
                detail_url = user[0]
                # print(detail_url)
                data = get_detail(detail_url)

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

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 文件即可,完整代码参照:

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 文件的存储(带列头),简单多线程的应用。