NBA 球员数据采集(1)

NBA 球员数据采集测验

为了完成本关任务,你需要掌握:

数据获取简介

数据采集

本关采用 requests 库完成数据采集。

数据获取简介
url 地址: https://www.basketball-reference.com/players/a/

请求头:

进入网站后,等待网页加载完毕,点击 F12 或右击选择检查,搜索找到页面 a/,设置请求头信息。


请求头图

网页主界面如下图所示:


网页主界面

在本次教学中,我们需要获取所有 A 姓球员的基本数据和一些详细数据。

基本数据就是从网页主界面中获取的数据,如下图所示:


基本数据图

我们需要在网页主界面中获取球员的姓名、位置、身高。由于体重列表栏中部分球员存在空值,我们使用 Xpath 解析后会导致排列顺序混乱,所以体重数据我们在球员详情页中获取。

我们在网页主界面中点击 F12 或右击选择检查,查看球员详情页链接:


球员详情页链接获取图

可以直接获取到球员详情页链接的后缀。

在球员详情页中,我们需要获取球员的详细数据:


详细数据图

所有需要获取的字段信息如下:

字段名    解释    获取信息说明
id    球员 id    解析球员详情页链接获取,如:https://www.basketball-reference.com/players/a/abdelal01.html,则 id 为:abdelal01
info_url    球员详情信息网址    网站首页表格中 Player 列
player_name    球员姓名    网站首页表格中 Player 列
player_pos    战术位置    网站首页表格中 Pos 列
player_ht    身高(英尺)    网站首页表格中 Ht 列
player_wt    体重(磅)    球员详情页(网站首页的体重列中存在空值)
player_age    球员年龄    球员详情页,动态加载数据,需要手动计算。(格式与网址保持一致)
country    国籍    球员详情页(大写)
college    就读大学    球员详情页
high_school    就读高中    球员详情页
rank_year    同届排名    球员详情页
draft    选秀信息    球员详情页
draft_date    选秀日期    球员详情页
work_year    经验    球员详情页
team_count    效力球队数量    球员详情页
last_team_name    最后效力球队    球员详情页
season    赛季    球员详情页
games_count    场次    球员详情页
PTS    场均得分    球员详情页
TRB    场均篮板    球员详情页
AST    场均助攻    球员详情页
FG    投篮命中率    球员详情页
FG3    三分球命中率    球员详情页
FT    罚球命中率    球员详情页
EFG    有效命中率    球员详情页
PER    效率值    球员详情页
WS    胜率    球员详情页
firstTime    首秀时间    球员详情页中表格内的season列,其第一个赛季链接中第一比赛上场时间。
lastTime    退役时间    球员详情页中表格内的season列,其最后一个赛季链接中最后一场比赛上场时间。
数据采集
首先,我们定义相关全局变量,建立初始化函数和入口函数。

全局变量

save_fp = open("./nba_data.csv", "w", encoding="utf-8-sig", newline="")  # 创建存储文件对象
csv_writer = csv.writer(save_fp)  # 创建 csv 对象
start_time = int(time.time())  # 记录开始时间
main_response = None  # 记录主页面内容
main_count = 0  # 记录主页面当前循环次数
total_count = 0  # 记录当前页面总循环次数
初始化函数

写入 csv 文件表头。

def open_spider(csv_writer):
    print("--------------------------开始爬取--------------------------")
    header = [
        "id", "info_url", "player_name", "player_pos", "player_ht", "player_wt", "player_age",
        "country", "college", "high_school", "rank_year", "draft", "draft_date",
        "work_year", "team_count", "last_team_name", "season", "games_count", "PTS",
        "TRB", "AST", "FG", "FG3", "FT", "EFG", "PER", "WS", "firstTime", "lastTime"
    ]
    
    csv_writer.writerow(header)
入口函数

if __name__ == '__main__':
    # 调用初始化函数
    open_spider(csv_writer=csv_writer)
    # 主界面 url
    start_urls = 'https://www.basketball-reference.com/players/a/'
    
    # 请求头,能正常访问则无需设置。
    headers = {
        "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
        "accept-encoding": "gzip, deflate, br",
        "accept-language":"zh-CN,zh;q=0.9,en;q=0.8",
        "cache-control": "max-age=0",
        "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36",
        "cookie": "__qca=P0-1886065764-1658383903526; hubspotutk=c69f86272b8d6b7951413faad4bbba8e; _gcl_au=1.1.1479764202.1658383906; _fssid=4eb62bce-9135-4575-8c6c-5d8ad0cbf2f4; _cc_id=183aa1de207640f3684240685f5542ec; _gid=GA1.2.241408601.1665543062; fs.session.id=28b82855-63f1-4e4d-91e6-7b89700f6372; _pbjs_userid_consent_data=3524755945110770; cookie=0f478402-4db9-4a70-b701-97f74220b4bc; _lr_env_src_ats=false; _fbp=fb.1.1665624118904.1085698668; meta_more_button=1; sr_note_box_countdown=0; srcssfull=yes; is_live=true; __gpi=UID=000007ecf062bd3b:T=1658383923:RT=1665709635:S=ALNI_Mb2bDIonimZiSivHyVtJXd-izqHOA; fs.bot.check=true; __hssrc=1; __cf_bm=EmPpD3muqzz_31sAtEV0s4gGLvIdFhi6Shpzyu6SgfY-1665729535-0-AWm21C7A17no++kUeumsEqsLsuuBhWdoJfZOSjm84UE4KRKzbBnHrW7CjXx09VK80YmgWIrmVGgGufu+RC7CiiM=; __hstc=180814520.c69f86272b8d6b7951413faad4bbba8e.1658383903774.1665709602912.1665729511448.44; __hssc=180814520.1.1665729511448; _ga_NR1HN85GXQ=GS1.1.1665729503.38.1.1665730082.0.0.0; _ga=GA1.2.1217857762.1658383900; _gat_gtag_UA_1890630_2=1; _gat_gtag_UA_1890630_9=1; __gads=ID=8491b8023e0f5ed5:T=1658383923:S=ALNI_MaR4IK8peM0OjZI9ohiD4Ayvp-YjQ; _lr_retry_request=true; cto_bundle=KErEG19BMG1DTUFvZTB1ZURsWXlUTFc4RTRJUkEwWlI2MlBnYTdkVXhNQ3F2ekNWYkJ3QUhJc3N0V0RqYm9YRkU1T1p2eUZVUXd2MmQ1c1RCJTJCV3ZUNzBVQlFra2VPc0s5amlob1RRZG9yd3JhJTJGOXpyVHhGd1JIaDdvNm1yMVAlMkI0M21RSXI4dU9GU25MY1RkSTdWU2dsSU8wYTZYOTNkUlhUUyUyRlVPZDAxd1Y5Vzd0dyUzRA; cto_bidid=zXPnyV8xOVFMMlJzTHRsak4lMkJiemlZc0tQdG44WTNWdTI3a25qNG9YcU1QTURSJTJGd0dWOHdiWUVXVnp5Tm1qellmWGZUREppVU11Y0FpNDhzQUtJallkd3ViUU5lNkFoTTk5OENDWXpDZEtZY2U1RkV0UjlaRWhvNTNMcmM1dU9XY0RkUUc",
        "if-modified-since": "Fri, 14 Oct 2022 06:38:55 GMT",
        "sec-ch-ua": '"Chromium";v="106", "Google Chrome";v="106", "Not;A=Brand";v="99"',
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "Windows",
        "sec-fetch-dest": "document",
        "sec-fetch-mode": "navigate",
        "sec-fetch-site": "none",
        "sec-fetch-user": "?1",
        "upgrade-insecure-requests": "1"
    }
   
    # 发起请求
    page = requests.get(url=start_urls,headers=None,timeout=30)
    
    # 获取响应内容
    page_text = page.text
    
    response = etree.HTML(page_text) # 封装内容,便于使用 Xpath 处理
    main_response = response  # 记录主界面内容
        
    parse(response, main_count) # 调用主界面解析函数
现在我们来根据获取的字段对主界面进行解析,创建主界面解析函数。

主界面解析函数

解析传入的主界面内容,获取球员详情页链接、名称、位置、身高,由于首页的体重可能为空,所以不在首页这里获取体重。

通过获取的球员详情页链接列表来循环发起请求。

def parse(response, count):
    try:
        print("【主页面解析】")
        # 首页球员详情页链接列表
        list_href = response.xpath("//table[@id='players']/tbody/tr/th//a/@href")
        # 首页球员名称列表
        list_name = response.xpath("//table[@id='players']/tbody/tr/th//a/text()")
        # 首页球员位置列表
        list_pos = response.xpath("//table[@id='players']/tbody/tr/td[@data-stat='pos']/text()")
        # 首页球员身高列表
        list_ht = response.xpath("//table[@id='players']/tbody/tr/td[@data-stat='height']/text()")
        # 校验列表长度是否一致,如果不一致则说明中间有空值数据,则该列需要去详情页获取数据。
        # 长度一致才表明可以做到一一对应。
        print("长度校验:")
        print(len(list_href))
        print(len(list_name))
        print(len(list_pos))
        print(len(list_ht))
        # 存放首页获取到的每一个球员数据列表
        list_res = []
        # id正则,通过球员详情页链接获取球员 id
        rex = re.compile(r".*/(.*).html")
        # 循环列表,按顺序添加到球员数据列表中
        for i in range(0, len(list_name)):
            temp = [rex.findall(list_href[i])[0], "https://www.basketball-reference.com" + list_href[i],
                    list_name[i],
                    list_pos[i], list_ht[i]]
            list_res.append(temp)
        # 循环发起球员详情页数据请求
        total_count = len(list_res)  # 记录总的循环数
        for m in range(count, total_count):
            main_count = m  # 记录当前请求数
            page_text = requests.get(url=list_res[m][1], timeout=20).text
            response = etree.HTML(page_text)
            # 调用球员详情页解析函数
            parse_detail(response, list_res[m])
            time.sleep(0.3)  # 防止速度过快导致 IP 被封
    except Exception as ex:
        # 跳过请求发生异常的链接,进入下一次循环
        print("---------------发生异常---------------")
        print(ex)
        parse(main_response, main_count + 1)
球员详情页解析

解析球员详情页数据,根据获取字段信息来获取数据。

def parse_detail(response, data):
    try:
        print("【球员详情页解析】")
        playerData = data
        # 定义获取的字段,注意:部分球员中的字段不全。
        college = None  # 大学
        high_school = None  # 高中就读地
        rank_year = None  # 同届排名
        draft = None  # 选秀信息
        draft_date = None  # 选秀日期
        work_year = None  # 经验 Career Length 与 Experience 合为一列
        country = None  # 国籍
        age = None  # 年龄
        wt = None  # 体重
        # 通过观察球员详情页数据,可以发现,球员信息标签 <p> 的个数不同,且 <p> 没有任何属性作为标识,直接使用 xpath 解析到的数据肯定有误!
        # 解决方法: 可以发现,每个 <p> 标签中都有一个 <strong> 标签,且其内容并不重复,这样我们就可以循环的去判断匹配 <strong> 标签中的内容,
        # 如果符合,则通过下标解析出对应数据。
        # 观察 <p> 标签的最大数,将循环最大次数设为 15(但不一定要15,给一个相对的最大值即可)
        for i in range(1, 15):
            print(i)
            # 判断 <strong> 标签中值的是否为空,如果为空,则跳过此次循环。
            temp = response.xpath(
                "//div[@id='info']/div[@id='meta']//p[{}]/strong/text() | //div[@id='info']/div[@id='meta']//p[{}]//text()".format(
                    i, i))
            print(temp)
            if temp is None or temp == [] or temp == "":
                continue
            else:
                # 去除多余字符,进行字段匹配。
                for j in range(0, len(temp)):
                    temp[j] = temp[j].replace(" ", "").replace("\n", "").replace(":", "")
            # TODO 获取数据
            # 获取大学名称
            if "Colleges" in temp or "College" in temp:
                college = response.xpath(
                    "//div[@id='info']/div[@id='meta']//p[{}]/a//text()".format(i))
       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值