本地手集博客id“升级”在线抓取——“精致”版——(2024年终总结1.2)

从优快云文章列表API读取HTML文本,re.findall提取data并以str基础知识整理后写入磁盘文件(自定义csv格式文本)。


(笔记模板由python脚本于2025年01月12日 18:00:04创建,本篇笔记适合csdn的基础编程博主的coder翻阅)


【学习的细节是欢悦的历程】



  自学并不是什么神秘的东西,一个人一辈子自学的时间总是比在学校学习的时间长,没有老师的时候总是比有老师的时候多。
            —— 华罗庚


---== 彰显C位 ==---

  1. ,可以在评论区书写任何想法
  2. (我将在看到的第一时间回应)
  3. 点击这里回转《我的2024》

---== 彰显C位 ==---

等风来,不如追风去……

我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


从文章列表api读取html文本
优快云博文id在线搜集
(re.findall提取data并以str基础整理)



我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


本文质量分:

96 96 96

本文地址: https://blog.youkuaiyun.com/m0_57158496/article/details/145096552

优快云质量分查询入口:http://www.youkuaiyun.com/qc


目 录

  • ◆ 优快云博文id在线搜集
    • 1、前言
    • 2、代码导读
      • a a a. 导入模块
      • b b b. 定义函数
      • c c c. 基础变量
      • d d d. 主程序
      • e e e. 代码优化
    • 3、完整源码(Python)


◆ 优快云博文id在线搜集


1、前言


  对于我的get_blog_ids,上一篇文章匆匆发坝了一个丑丑的版本,是为应景“年终总结”,总是意犹未尽,时常挂念。😜

  经过这几天抽空修磨,总算有了点儿“”的模范儿。😜虽然算不得精致,但还算可以讨喜目前 L e v e l Level Level的我的了。

  我在正文开始之前的聒噪,仅为说明一下我这篇“代码导读”文本产生的“语境”。😋


  • 运行效果截屏图片
    “静默”模式
    在这里插入图片描述
    “终端展示”模式
    在这里插入图片描述

输出的csv文本

数据写入时间:2025-01-12 18:57:10

               【“梦幻精灵_cq”的Blog】 


-------------- 博客等级:7 ---------------

访问量:75.84w 粉丝数:9.45k 周排名:2.37k 总排名:2.97k 积分:1.03w

博客总数:444 原创:441 获赞:4.22k 评论:579 收藏:4.77k

-------------- 共计:444篇 ---------------


      阅读总量:68.21w,总计评论:579      
           (平均阅读量:1.54k)            

==========================================

******************************************

上面是博客基本信息,下面是csv数据——
Id\\Title\\Summary\\Date\\Readed\\Comment
122566500\\让QQ群昵称色变的神奇代码\\我的优快云主页My Python 学习个人备忘录我的博文推荐让QQ群昵称色变的神奇代码   今天在\\2022-01-18 19:15:08\\62706\\17
128271700\\Python列表(list)反序(降序)的7种实现方式\\Python列表(list)原址反序的方法(本文获得优快云质量评分【x】)【学习的细节是欢悦的历程\\2022-12-11 23:54:15\\14075\\8
124244618\\个人信息提取(字符串)\\个人信息提取(字符串)\\2022-04-18 11:07:12\\10398\\0
...
(后续数据行略)



我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


2、代码导读


a a a. 导入模块

from urllib import request, error
from re import compile as re_compile, split as re_split, DOTALL
from datetime import datetime

  • 导入了urllib模块中的requesterror,用于发送网络请求和处理请求错误。
  • 导入了re模块中的compilesplitDOTALL,用于正则表达式编译、字符串分割以及匹配任意字符包括换行符。
  • 导入了datetime模块,用于处理日期和时间。

b b b. 定义函数


def opener_get(url: str, opener) -> str:
    ''' Read htmlcode '''
    
    try:
        with opener.open(url) as respone:
            return  respone.read().decode('utf-8') # 以utf-8编码读取页面
    except error.HTTPError as e:
        raise Exception(
        	    f"HTTPError: The server couldn't fulfill the request."
        	    f"\nError code: {e.code}"
        	        )
    except error.URLError as e:
        raise Exception(
        	    f"URLError: Failed to reach a server."
            	f"\nReason: {e.reason}"
            	    )
    except Exception as e:
        raise Exception(
        	    f"An unexpected error occurred: "
        	    f"\n{e}")

  • opener_get(url: str, opener) -> str
    • 用于读取网页的HTML代码。
    • 处理HTTP错误和URL错误,并在发生异常时抛出错误信息。


def get_blog_info(doc_html: str) -> dict:
    ''' Extract blog_info from pagelist '''
    pattern = (
        r'''(?:<li class="active margin" id="container-header-blog" data-type="blog" data-num="(\d+)">)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd class="font">(原创)</dd>)'''
        r'''.+?(?:<dl class="text-center" data-report-click='{"mod":"1598321000_002","spm":"1001.2101.3001.4311"}' title="(\d+)">.+?<dd class="font">(周排名)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd class="font">(总排名)</dd>)'''
        r'''.+?(?:<dl class="text-center" style="min-width:58px" title="(\d+)">.+?<dd>(访问)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)级,.+?<dd>(等级)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(积分)</dd>)'''
        r'''.+?(?:<dl class="text-center" id="fanBox" title="(\d+)">.+?<dd>(粉丝)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(获赞)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(评论)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(收藏)</dd>)'''
               ) # 原字符串每行一个数据:除第一项是博客总数外,其余数据紧接的中文字符提取就是其key
    pattern = re_compile(pattern, DOTALL) # 通配字符串re编译
    return pattern.findall(doc_html) # info提取

  • get_blog_info(doc_html: str) -> dict
    • 从博客列表页面中提取博客信息。
    • 使用正则表达式匹配并返回一个包含博客信息的字典。


def get_blog_lists() -> list:
    ''' Read blog all listpage htmlcode '''
    print(f"{' Reading the BlogList Page  01 …… ':-^42}", end='\r')
    opener = request.build_opener(request.HTTPHandler) # 创建一段时间内的持久连接urllib.request.HTTPHandler对象,是对上版本每次创建连接的优化
    url = f"{url_base}list/1" # 首页列表地址
    doc_blogs = [opener_get(url, opener)] # 读取第一页
    info = get_blog_info(doc_blogs[0]) # 提取blog_info

    if info:
        n = int(info[0][0])/40 # 计算列表页面数,目前csdn设定每页40个文章数据
        n = int(n)+1 if n > n//1 else int(n) # 向上取整修正blog_list总页数
    else:
        raise ValueError(
            	f"\n\n{' Not Extracted Bloginfo. ':-^42}"
            	f"\n{'(Please check htmlcode CHANGED.)':-^42}"
                        	)
    
    for page in range(2, n+1):
        print(f"{f' Reading the BlogList Page {page:0>2} …… ':-^42}", end='\r')
        url = f"{url_base}list/{page}"
        req = request.Request(url, headers=headers) # 读取html源码文本
        doc_blogs.append(opener_get(req, opener)) # 追加读取页面源码字符串

    return doc_blogs, info # 返回所有列表页面字符串list和提取的blog_info

  • get_blog_lists() -> list
    • 读取所有博客列表页面的HTML代码。
    • 计算博客列表页面的总数,并逐页读取。


def get_blog_ids(doc_html: str) -> list:
    ''' Extract blog_ids '''
    return pattern.findall(doc_html)

  • get_blog_ids(doc_html: str) -> list
    • 从HTML代码中提取博客ID列表。


def structure_bloginfo(info: list) -> dict:
    ''' Dict info '''
    info = info[0] # info为仅一个tuple的list
    n = len(info) # 计算info长度
    keys = ['博客总数'] + [info[i] for i in range(2, n, 2)] # 对齐字段
    values = [info[0]] + [info[i] for i in range(1, n, 2)] # 拆分数据Value
    return dict(zip(keys, map(int, values))) # 返回结构的字典info

  • structure_bloginfo(info: list) -> dict
    • 将博客信息列表转换为字典格式。


def strfinfo(data: dict) -> str:
    ''' Strfing info '''
    d = {}

    for key in data: # 遍历键值
        value = data[key]
        d[key] = f"{value/10000:.2f}w" if value>10000 else f"{value/1000:.2f}k" if value>1000 else value # 格式化数值

    level = f" 博客等级:{d.get('等级')} "
    return (
        	f"\n{level:-^37}"
        	f"\n\n访问量:{d.get('访问')} 粉丝数:{d.get('粉丝')} 周排名:{d.get('周排名')} 总排名:{d.get('总排名')} 积分:{d.get('积分')}"
        	f"\n\n博客总数:{d.get('博客总数')} 原创:{d.get('原创')} 获赞:{d.get('获赞')} 评论:{d.get('评论')} 收藏:{d.get('收藏')}"
    	        )

  • strfinfo(data: dict) -> str
    • 格式化博客信息字典,以便打印输出。


def data_clear(data: list) -> list:
    ''' Data clearing '''
    for k,blog_id in enumerate(data): # 阅读数、评论数转整
        blog_id = list(blog_id) # to list,方便“篡改”
        blog_id[2] = re_split(r'[。.!!??…...]', blog_id[2])[0][:50] # 摘取抓取摘要第一句话,长度50个字符,超出将被截断(用中英文句末标点分割)
        blog_id[-2] = int(blog_id[-2]) # int阅读量
        blog_id[-1] = int(blog_id[-1]) if blog_id[-1] else 0 # 没有评论,置0
        data[k] = tuple(blog_id) # to tuple,安全保存

    data.sort(key=lambda x: x[-2], reverse=True) # 接阅读量排逆序
    return data

  • data_clear(data: list) -> list
    • 清洗博客数据,包括阅读数和评论数的转换,以及摘要的截取。


def organize_data(blog_ids: list, nickname, info: list) -> None:
    ''' Organize Data '''
    blog_ids = data_clear(blog_ids) # 清洗数据
    readeds = sum([blog_id[-2] for blog_id in blog_ids]) # 计算博文总阅读量
    comments = sum([blog_id[-1] for blog_id in blog_ids]) # 计算博文总评论数
    comments = f"{comments/1000:.2f}k" if comments>1000 else comments # 格式化评论数值
    dict_info = structure_bloginfo(info) # dict结构info
    blog_info = strfinfo(dict_info) # 格式化打印info
    count = (
          f"\n{f' 共计:{len(blog_ids)}篇 ':-^38}"
          f"\n\n\n{f'阅读总量:{readeds/10000:.2f}w,总计评论:{comments}':^32}"
          f"\n{f'(平均阅读量:{readeds/len(blog_ids)/1000:.2f}k)':^36}"
                       ) # 海象运算符原址赋值,方便写入磁盘文件
    if flag != '':
        print(f"{blog_info}\n{count}") # 格式化结果终端打印

    # Writer,数据写入磁盘文件 #
    try:
        writer(r'\\', blog_ids, nickname, blog_info, count) # 数据写入磁盘
        print(f"\n{' Data writed ':-^42}")
    except Exception as e:
        raise Exception(
    	     f"\n\n{' WriteError ':=^42}"
    	     f"\n\n{e}"
    	     f"\n\n{' Please Name&Path Error ':=^42}"
    	         )

  • organize_data(blog_ids: list, nickname, info: list) -> None
    • 整理博客数据,包括数据清洗、计算总阅读量和总评论数。
    • 将整理后的数据写入磁盘文件。


def writer(separator: str, blog_ids: list, nickname: str, info: str, count: str) -> None:
    ''' Write data in file '''
    filename = f"{path}csdn_blog_ids{datetime.now().strftime('%d%H%M%S')}.csv" # 以当前日时分秒时间截字符串“唯一”文件名    
    with open(filename, 'w', newline='') as f:
        header = 'Id', 'Title', 'Summary','Date', 'Readed' , 'Comment'
        f.write(
        	    f"数据写入时间:{datetime.now().strftime(date_format)}"
        	    f"{nickname}"
       	     f"\n\n{info}"
       	     f"\n{count}"
       	     f"\n\n{'':=^42}"
       	          )
        f.write(
        	     f"\n\n{'*'*42}" # 分割线
        	     f"\n\n上面是博客基本信息,下面是csv数据——"
        	     f"\n{separator.join(header)}"
        	        ) # 写字段
        
        for blog_id in blog_ids:
            f.write(f"\n{separator.join(map(str, blog_id))}") # 写数据行

  • writer(separator: str, blog_ids: list, nickname: str, info: str, count: str) -> None
    • 将博客数据写入CSV文件。

c c c. 基础变量


# Base Var #
path = '/sdcard/Documents/csdn/temp/' # 磁盘文件目录,用于数据保存
date_format = '%Y-%m-%d %H:%M:%S'
time_format = '%H:%M:%S'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} # urllib.request.urlopen(url)一般可以缺省请求头,它会给默认一个请求头,但我初学,留个记念

  • 定义了文件保存路径、日期格式、时间格式、请求头等基础变量。

d d d. 主程序


# main #
flag = 's' # 静默开关,缺省's',不打印整理数据;其它字符打印。
print(f"{f' Today is {datetime.now().strftime(date_format)}. ':-^42}", end='\r') # 覆盖进度条
usrname = 'm0_57158496' # 可以接受csdn注册id个人主页定制域名字符串,如'kunzhi'(昆志说),'qq_44907926'(孤寒者),'qq_35190492'(敖丙)
print() # 打印空行
input_name = input(
    	     f"\n{' Input userName (Not Nockname) ':.^42}"
    	     f"\n{'(Default “m0_57158496” Please confirm)':^42}"
    	     f"\n{'':>16}"
    	         ).strip()
START = datetime.now() # 记录程序运算开始时间(第一版用的time库,这个第二版换用更方便的datetime)
usrname = input_name if input_name else usrname # 不输入用户id,缺省笔者自已
url_base = f"https://blog.youkuaiyun.com/{usrname}/article/" if usrname[4:].isdigit() else f"https://{usrname}.blog.youkuaiyun.com/article/" # 分别处理自定义主页地址和默认地址的用户名
print(f"\n{' Data Extracting…… ':-^42}", end='\r')

try:
    doc_blog_lists, blog_info = get_blog_lists() # 获取博客文章列表页面、个人博客基本信息
except Exception as e:
    input(
    	    f"{' UsernameError ':-^42}"
    	    f"\n\n{e}"
    	    f"\n\n{' AnyKey Exit ':-^42}"
    	        )
    print('\033[2J') # 用打印控制代码清屏
    exit((
    	    f"\n{' You are exited. ':-^42}"
    	    f"\n{'(Please try again...)':^42}\n"
    	       ))

nickname = re_compile(r'nickName = "(.+)";').findall(doc_blog_lists[0])[0] # 从第一页文章列表提取昵称字符串
nickname = f"\n\n{f' 【“{nickname}”的Blog】 ':>32}"
print(f"{f'':=^42}", end='\r') # 清除进度条

if flag != 's':
    print(nickname)

# 集中循环提取 #
pattern = (
    r'''<div class="article-item-box csdn-tracking-statistics" data-articleid="(\d+)">'''
    r'''.+?<span class="article-.+?</span>\s+(.+?)\s+</a>'''
    r'''.+?<p class="content">\s+(.+?)\s+</p>'''
    r'''.+?<span class="date">(.+?)</span>'''
    r'''.+?<span class="read-num">.+?/readCount.+?>(\d+)</span>'''
    r'''\s+(?:<span class="read-num">.+?/commentCount.+?>(\d+)</span>)*'''
            ) # 原字符串每行一个数据:Id、Title、Summary、Date、Readed、Comment
    # 简明版,程序用时:0.008107 秒(一页文章列表本地文件,表态解析)
pattern = re_compile(pattern, DOTALL) # 由于python的优化机制,同一pattern总是被仅编译一次,可以不担心重复编译。但习惯使然,我显式编译一次
'''
blog_ids = [] # blog_ids列表初值
for k,doc_html in enumerate(doc_blog_lists, start=1):
    page_ids = get_blog_ids(doc_html) # 提示单页文章列表数据(最多含40条数据)
    print(f"{f' Page: {k:0>2}, Ids: {len(page_ids)} ':-^42}", end='\r')
    blog_ids += page_ids # 追加page_ids
''' # 下面的解析式实现,是这段代码的浓缩。用三引号注释掉不清除,是为留个记念

# 解析式一键提取 #
print(f"{' Data Extracting… ':-^42}", end='\r')
blog_ids = sum([get_blog_ids(doc_html) for doc_html in doc_blog_lists], []) # 提取数据并用sum魔术拉平嵌套列表

# 终端格式化输出 #
print(' '*42) # 清除“进度条”

if blog_ids: # 有提取到数据,整理数据
    organize_data(blog_ids, nickname, blog_info) # 数据整理
else:
    print(
    	     f"\n\n{' Not finded data. ':-^42}"
    	     f"\n{'(Please check any thing...)':^42}"
    	     )

end = datetime.now()-START # timedelta对象:程序用时总秒数
print(
    f"\n{f' 程序用时:{end.seconds + end.microseconds/10**6:.2f} 秒 ':=^36}"
    f"\n\n{f' Now is {datetime.now().strftime(time_format)}. ':-^42}\n"
	     )

  • 设置静默开关flag,用于控制是否打印整理后的数据。
  • 读取用户输入的优快云用户名,并构建博客列表页面的URL。
  • 提取博客列表页面和个人博客基本信息。
  • 从博客列表页面中提取昵称。
  • 使用解析式一键提取所有博客ID和相关数据。
  • 格式化输出数据,并将数据写入磁盘文件。
  • 打印程序运行时间和当前时间。

e e e. 代码优化


'''
blog_ids = [] # blog_ids列表初值
for k,doc_html in enumerate(doc_blog_lists, start=1):
    page_ids = get_blog_ids(doc_html) # 提示单页文章列表数据(最多含40条数据)
    print(f"{f' Page: {k:0>2}, Ids: {len(page_ids)} ':-^42}", end='\r')
    blog_ids += page_ids # 追加page_ids
''' # 下面的解析式实现,是这段代码的浓缩。用三引号注释掉不清除,是为留个记念

# 解析式一键提取 #
print(f"{' Data Extracting… ':-^42}", end='\r')
blog_ids = sum([get_blog_ids(doc_html) for doc_html in doc_blog_lists], []) # 提取数据并用sum魔术拉平嵌套列表

  • 您对代码进行了优化,减少了行数,并使用了列表解析式来简化数据提取过程。

  通过仔细这份导读,您可以更清晰地了解代码的结构和功能,再结合我间杂于代码中的注释文字,相信可以更好的阅读代码。



我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


3、完整源码(Python)

(源码较长,点此跳过源码)

#!/usr/bin/env python3 # 本脚本代码基于python 3.x 撰写,请阅读和run时注意(本脚本已在python3.12.7运行通过)
print(f"\n{f' Loading dependencies… ':-^42}", end='\r')
from urllib import request,  error
from re import compile as re_compile, split as re_split, DOTALL
from datetime import datetime


# Modules #

def opener_get(url: str, opener) -> str:
    ''' Read htmlcode '''
    
    try:
        with opener.open(url) as respone:
            return  respone.read().decode('utf-8') # 以utf-8编码读取页面
    except error.HTTPError as e:
        raise Exception(
        	    f"HTTPError: The server couldn't fulfill the request."
        	    f"\nError code: {e.code}"
        	        )
    except error.URLError as e:
        raise Exception(
        	    f"URLError: Failed to reach a server."
            	f"\nReason: {e.reason}"
            	    )
    except Exception as e:
        raise Exception(
        	    f"An unexpected error occurred: "
        	    f"\n{e}")


def get_blog_info(doc_html: str) -> dict:
    ''' Extract blog_info from pagelist '''
    pattern = (
        r'''(?:<li class="active margin" id="container-header-blog" data-type="blog" data-num="(\d+)">)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd class="font">(原创)</dd>)'''
        r'''.+?(?:<dl class="text-center" data-report-click='{"mod":"1598321000_002","spm":"1001.2101.3001.4311"}' title="(\d+)">.+?<dd class="font">(周排名)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd class="font">(总排名)</dd>)'''
        r'''.+?(?:<dl class="text-center" style="min-width:58px" title="(\d+)">.+?<dd>(访问)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)级,.+?<dd>(等级)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(积分)</dd>)'''
        r'''.+?(?:<dl class="text-center" id="fanBox" title="(\d+)">.+?<dd>(粉丝)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(获赞)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(评论)</dd>)'''
        r'''.+?(?:<dl class="text-center" title="(\d+)">.+?<dd>(收藏)</dd>)'''
               ) # 原字符串每行一个数据:除第一项是博客总数外,其余数据紧接的中文字符提取就是其key
    pattern = re_compile(pattern, DOTALL) # 通配字符串re编译
    return pattern.findall(doc_html) # info提取


def get_blog_lists() -> list:
    ''' Read blog all listpage htmlcode '''
    print(f"{' Reading the BlogList Page  01 …… ':-^42}", end='\r')
    opener = request.build_opener(request.HTTPHandler) # 创建一段时间内的持久连接urllib.request.HTTPHandler对象,是对上版本每次创建连接的优化
    url = f"{url_base}list/1" # 首页列表地址
    doc_blogs = [opener_get(url, opener)] # 读取第一页
    info = get_blog_info(doc_blogs[0]) # 提取blog_info

    if info:
        n = int(info[0][0])/40 # 计算列表页面数,目前csdn设定每页40个文章数据
        n = int(n)+1 if n > n//1 else int(n) # 向上取整修正blog_list总页数
    else:
        raise ValueError(
            	f"\n\n{' Not Extracted Bloginfo. ':-^42}"
            	f"\n{'(Please check htmlcode CHANGED.)':-^42}"
                        	)
    
    for page in range(2, n+1):
        print(f"{f' Reading the BlogList Page {page:0>2} …… ':-^42}", end='\r')
        url = f"{url_base}list/{page}"
        req = request.Request(url, headers=headers) # 读取html源码文本
        doc_blogs.append(opener_get(req, opener)) # 追加读取页面源码字符串

    return doc_blogs, info # 返回所有列表页面字符串list和提取的blog_info


def get_blog_ids(doc_html: str) -> list:
    ''' Extract blog_ids '''
    return pattern.findall(doc_html)


def structure_bloginfo(info: list) -> dict:
    ''' Dict info '''
    info = info[0] # info为仅一个tuple的list
    n = len(info) # 计算info长度
    keys = ['博客总数'] + [info[i] for i in range(2, n, 2)] # 对齐字段
    values = [info[0]] + [info[i] for i in range(1, n, 2)] # 拆分数据Value
    return dict(zip(keys, map(int, values))) # 返回结构的字典info


def strfinfo(data: dict) -> str:
    ''' Strfing info '''
    d = {}

    for key in data: # 遍历键值
        value = data[key]
        d[key] = f"{value/10000:.2f}w" if value>10000 else f"{value/1000:.2f}k" if value>1000 else value # 格式化数值

    level = f" 博客等级:{d.get('等级')} "
    return (
        	f"\n{level:-^37}"
        	f"\n\n访问量:{d.get('访问')} 粉丝数:{d.get('粉丝')} 周排名:{d.get('周排名')} 总排名:{d.get('总排名')} 积分:{d.get('积分')}"
        	f"\n\n博客总数:{d.get('博客总数')} 原创:{d.get('原创')} 获赞:{d.get('获赞')} 评论:{d.get('评论')} 收藏:{d.get('收藏')}"
    	        )


def data_clear(data: list) -> list:
    ''' Data clearing '''
    for k,blog_id in enumerate(data): # 阅读数、评论数转整
        blog_id = list(blog_id) # to list,方便“篡改”
        blog_id[2] = re_split(r'[。.!!??…...]', blog_id[2])[0][:50] # 摘取抓取摘要第一句话,长度50个字符,超出将被截断(用中英文句末标点分割)
        blog_id[-2] = int(blog_id[-2]) # int阅读量
        blog_id[-1] = int(blog_id[-1]) if blog_id[-1] else 0 # 没有评论,置0
        data[k] = tuple(blog_id) # to tuple,安全保存

    data.sort(key=lambda x: x[-2], reverse=True) # 接阅读量排逆序
    return data


def organize_data(blog_ids: list, nickname, info: list) -> None:
    ''' Organize Data '''
    blog_ids = data_clear(blog_ids) # 清洗数据
    readeds = sum([blog_id[-2] for blog_id in blog_ids]) # 计算博文总阅读量
    comments = sum([blog_id[-1] for blog_id in blog_ids]) # 计算博文总评论数
    comments = f"{comments/1000:.2f}k" if comments>1000 else comments # 格式化评论数值
    dict_info = structure_bloginfo(info) # dict结构info
    blog_info = strfinfo(dict_info) # 格式化打印info
    count = (
          f"\n{f' 共计:{len(blog_ids)}篇 ':-^38}"
          f"\n\n\n{f'阅读总量:{readeds/10000:.2f}w,总计评论:{comments}':^32}"
          f"\n{f'(平均阅读量:{readeds/len(blog_ids)/1000:.2f}k)':^36}"
                       ) # 海象运算符原址赋值,方便写入磁盘文件
    if flag != 's':
        print(f"{blog_info}\n{count}") # 格式化结果终端打印

    # Writer,数据写入磁盘文件 #
    try:
        writer(r'\\', blog_ids, nickname, blog_info, count) # 数据写入磁盘
        print(f"\n{' Data writed ':-^42}")
    except Exception as e:
        raise Exception(
    	     f"\n\n{' WriteError ':=^42}"
    	     f"\n\n{e}"
    	     f"\n\n{' Please Name&Path Error ':=^42}"
    	         )


def writer(separator: str, blog_ids: list, nickname: str, info: str, count: str) -> None:
    ''' Write data in file '''
    filename = f"{path}csdn_blog_ids{datetime.now().strftime('%d%H%M%S')}.csv" # 以当前日时分秒时间截字符串“唯一”文件名    
    with open(filename, 'w', newline='') as f:
        header = 'Id', 'Title', 'Summary','Date', 'Readed' , 'Comment'
        f.write(
        	    f"数据写入时间:{datetime.now().strftime(date_format)}"
        	    f"{nickname}"
       	     f"\n\n{info}"
       	     f"\n{count}"
       	     f"\n\n{'':=^42}"
       	          )
        f.write(
        	     f"\n\n{'*'*42}" # 分割线
        	     f"\n\n上面是博客基本信息,下面是csv数据——"
        	     f"\n{separator.join(header)}"
        	        ) # 写字段
        
        for blog_id in blog_ids:
            f.write(f"\n{separator.join(map(str, blog_id))}") # 写数据行


# Base Var #
path = '/sdcard/Documents/csdn/temp/' # 磁盘文件目录,用于数据保存
date_format = '%Y-%m-%d %H:%M:%S'
time_format = '%H:%M:%S'
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'} # urllib.request.urlopen(url)一般可以缺省请求头,它会给默认一个请求头,但我初学,留个记念


# main #
flag = 's' # 静默开关,缺省's',不打印整理数据;其它字符打印。
print(f"{f' Today is {datetime.now().strftime(date_format)}. ':-^42}", end='\r') # 覆盖进度条
usrname = 'm0_57158496' # 可以接受csdn注册id个人主页定制域名字符串,如'kunzhi'(昆志说),'qq_44907926'(孤寒者),'qq_35190492'(敖丙)
print() # 打印空行
input_name = input(
    	     f"\n{' Input userName (Not Nockname) ':.^42}"
    	     f"\n{'(Default “m0_57158496” Please confirm)':^42}"
    	     f"\n{'':>16}"
    	         ).strip()
START = datetime.now() # 记录程序运算开始时间(第一版用的time库,这个第二版换用更方便的datetime)
usrname = input_name if input_name else usrname # 不输入用户id,缺省笔者自已
url_base = f"https://blog.youkuaiyun.com/{usrname}/article/" if usrname[4:].isdigit() else f"https://{usrname}.blog.youkuaiyun.com/article/" # 分别处理自定义主页地址和默认地址的用户名
print(f"\n{' Data Extracting…… ':-^42}", end='\r')

try:
    doc_blog_lists, blog_info = get_blog_lists() # 获取博客文章列表页面、个人博客基本信息
except Exception as e:
    input(
    	    f"{' UsernameError ':-^42}"
    	    f"\n\n{e}"
    	    f"\n\n{' AnyKey Exit ':-^42}"
    	        )
    print('\033[2J') # 用打印控制代码清屏
    exit((
    	    f"\n{' You are exited. ':-^42}"
    	    f"\n{'(Please try again...)':^42}\n"
    	       ))

nickname = re_compile(r'nickName = "(.+)";').findall(doc_blog_lists[0])[0] # 从第一页文章列表提取昵称字符串
nickname = f"\n\n{f' 【“{nickname}”的Blog】 ':>32}"
print(f"{f'':=^42}", end='\r') # 清除进度条

if flag != 's':
    print(nickname)

# 集中循环提取 #
pattern = (
    r'''<div class="article-item-box csdn-tracking-statistics" data-articleid="(\d+)">'''
    r'''.+?<span class="article-.+?</span>\s+(.+?)\s+</a>'''
    r'''.+?<p class="content">\s+(.+?)\s+</p>'''
    r'''.+?<span class="date">(.+?)</span>'''
    r'''.+?<span class="read-num">.+?/readCount.+?>(\d+)</span>'''
    r'''\s+(?:<span class="read-num">.+?/commentCount.+?>(\d+)</span>)*'''
            ) # 原字符串每行一个数据:Id、Title、Summary、Date、Readed、Comment
    # 简明版,程序用时:0.008107 秒(一页文章列表本地文件,表态解析)
pattern = re_compile(pattern, DOTALL) # 由于python的优化机制,同一pattern总是被仅编译一次,可以不担心重复编译。但习惯使然,我显式编译一次
'''
blog_ids = [] # blog_ids列表初值
for k,doc_html in enumerate(doc_blog_lists, start=1):
    page_ids = get_blog_ids(doc_html) # 提示单页文章列表数据(最多含40条数据)
    print(f"{f' Page: {k:0>2}, Ids: {len(page_ids)} ':-^42}", end='\r')
    blog_ids += page_ids # 追加page_ids
''' # 下面的解析式实现,是这段代码的浓缩。用三引号注释掉不清除,是为留个记念

# 解析式一键提取 #
print(f"{' Data Extracting… ':-^42}", end='\r')
blog_ids = sum([get_blog_ids(doc_html) for doc_html in doc_blog_lists], []) # 提取数据并用sum魔术拉平嵌套列表

# 终端格式化输出 #
print(' '*42) # 清除“进度条”

if blog_ids: # 有提取到数据,整理数据
    organize_data(blog_ids, nickname, blog_info) # 数据整理
else:
    print(
    	     f"\n\n{' Not finded data. ':-^42}"
    	     f"\n{'(Please check any thing...)':^42}"
    	     )

end = datetime.now()-START # timedelta对象:程序用时总秒数
print(
    f"\n{f' 程序用时:{end.seconds + end.microseconds/10**6:.2f} 秒 ':=^36}"
    f"\n\n{f' Now is {datetime.now().strftime(time_format)}. ':-^42}\n"
	     )


点击这里回转 《我的2024》



我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


上一篇:  本地手集博客id“升级”在线抓取——简陋版——(2024年终总结1.1)(我之前每每发布笔记都用csv纯文本记录,一个机缘巧得文章列表api实现在线整理自已的文章阅读量数据)
下一篇: 



我的HOT博:

  本次共计收集404篇博文笔记信息,总阅读量61.76w。数据采集于2024年11月25日 08:23:38,用时7分56.4秒。阅读量不小于6.00k的有 9 9 9篇。

  1. 让QQ群昵称色变的神奇代码
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/122566500
    浏览阅读:6.2w
    点赞:25 收藏:89 评论:17
    (本篇笔记于2022-01-18 19:15:08首次发布,最后修改于2022-01-20 07:56:47)

  2. Python列表(list)反序(降序)的7种实现方式
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/128271700
    浏览阅读:1.3w
    点赞:9 收藏:40 评论:8
    (本篇笔记于2022-12-11 23:54:15首次发布,最后修改于2023-03-20 18:13:55)

  3. pandas 数据类型之 DataFrame
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/124525814
    浏览阅读:1.0w
    点赞:7 收藏:40 
    (本篇笔记于2022-05-01 13:20:17首次发布,最后修改于2022-05-08 08:46:13)

  4. 个人信息提取(字符串)
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/124244618
    浏览阅读:1.0w
    点赞:3 收藏:20 
    (本篇笔记于2022-04-18 11:07:12首次发布,最后修改于2022-04-20 13:17:54)

  5. 罗马数字转换器|罗马数字生成器
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/122592047
    浏览阅读:8.2k
    收藏:3 
    (本篇笔记于2022-01-19 23:26:42首次发布,最后修改于2022-01-21 18:37:46)

  6. 统计字符串字符出现的次数
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/130517025
    浏览阅读:8.1k
    点赞:5 收藏:24 
    (本篇笔记于2023-05-06 22:28:18首次发布,最后修改于2023-05-12 06:21:40)

  7. Python字符串居中显示
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/122163023
    浏览阅读:8.0k
    点赞:1 收藏:12 评论:1
  8. 回车符、换行符和回车换行符
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/123109488
    浏览阅读:6.7k
    点赞:2 收藏:4 
    (本篇笔记于2022-02-24 13:10:02首次发布,最后修改于2022-02-25 20:07:40)

  9. python清屏
    地址:https://blog.youkuaiyun.com/m0_57158496/article/details/120762101
    浏览阅读:6.1k
    点赞:1 收藏:10 

推荐条件 阅读量突破6.00k
(更多热博,请点击蓝色文字跳转翻阅)

  • 截屏图片
    在这里插入图片描述
      (此文涉及ChatPT,曾被csdn多次下架,前几日又因新发笔记被误杀而落马。躺“未过审”还不如回收站,回收站还不如永久不见。😪值此年底清扫,果断移除。留此截图,以识“曾经”。2023-12-31)



我的 2 0 2 4  我的 2 0 2 4  我的 2 0 2 4


老齐漫画头像

精品文章:

来源:老齐教室


Python 入门指南【Python 3.6.3】


好文力荐:


优快云实用技巧博文:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦幻精灵_cq

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值