吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1323|回复: 14
收起左侧

[Python 原创] 【思路&源码】上海高二物理竞赛试题的爬取

[复制链接]
icer233 发表于 2024-8-25 13:10

需求

爬取竞赛试题_上海高考网 (gaokao.com)这个页面下所有的试题文件

初出茅庐

我们直接抓取这个页面,解析出每个详情链接和文件名称

ph

# -*- coding:utf-8 -*-
import requests
from lxml import etree

url = 'http://sh.gaokao.com/gzjs/gewl/gewlst/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0'
}

# 创建文件目录实例
page_text = requests.get(url=url, headers=headers).text
tree = etree.HTML(page_text)
li_list = tree.xpath('//div[@id="main"]//ul[@class="text_list1"]/li')

for li in li_list:
    detail_url = li.xpath('./div[@class="title"]/a/@href')[0] # 获取详情页链接
    name = li.xpath('./div[@class="title"]/a/text()')[0]  # 获取文件名

第一难: 乱码的中文

我们把名字和链接输出一下看看能不能正常获取

print(name, detail_url)

看一下结果,发现中文乱码了,但是应该都正确地获取了

ph

我们来处理一下中文编码问题

打开网页源代码, 找到表头

ph

这就好办了, 改一下编码

response = requests.get(url=url, headers=headers)
response.encoding = 'gb2312'
page_text = response.text

重新运行程序, 乱码问题解决了

ph

然后我们抓取详情页, 分析一下下载链接的位置

ph

随便打开几个, 发现每个页面结构都有些不同, 但是好在只有一个rar文件下载链接, xpath已经不合适了, 考虑用正则表达式

import re
detail_text = requests.get(url=detail_url, headers=headers).text
down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)', detail_text, re.S)[0] # 发现每个页面框架不一样,但是只有一个rar文件

第二难: 超时的响应

输出一下下载链接, 看看有没有问题

print(down_url)

等了半天, 输出了几条, 最后报错了 (运气不好的话可能一条都没输出直接报错)

输出了部分说明语法和逻辑方面没什么问题

urllib3.exceptions.ProtocolError: Response ended prematurely

requests.exceptions.ChunkedEncodingError: Response ended prematurely

如果你注意到细节的话, 可以发现当你点进详情页的时候, 虽然内容很快呈现, 但是标签页上还在转圈, 这与你的网速有关, 但我认为更主要的是服务器不稳定

没有什么特别好的方法, 只能在GET请求的时候加一个长一点的超时

detail_text = requests.get(url=detail_url, headers=headers, timeout=300).text

第三难: 避免重复下载

既然超时无法避免, 我们需要一些策略来避免一些重复的操作, 比如将已经下载好的文件重新下载一遍

原本的下载文件:

# 下载并保存rar文件
data = requests.get(url=down_url, headers=headers, timeout=180).content
    with open(ans_path, 'wb') as fp:
        fp.write(data)
        print(name, '下载成功')

我们直接在解析文件下载链接之前套一个简陋的if判断是否已经下载

if not os.path.exists(ans_path):
        # 获取文件下载链接
        detail_text = requests.get(url=detail_url, headers=headers, timeout=300).text
        down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)', detail_text, re.S)[0] # 发现每个页面框架不一样,但是只有一个rar文件

        # 下载并保存rar文件
        data = requests.get(url=down_url, headers=headers, timeout=180).content
        with open(ans_path, 'wb') as fp:
            fp.write(data)
            print(name, '下载成功')
    else:
        print(name, '已存在')

第四难: 用线程池增加成功率(玄学)

我们用多线程来下载, 先将我们的下载代码封装成一个函数

import os
def down(li):
    detail_url = li.xpath('./div[@class="title"]/a/@href')[0] # 获取详情页链接
    name = li.xpath('./div[@class="title"]/a/text()')[0]  # 获取文件名
    ans_path = './phy/' + name +'.rar' # 生成文件路径

    if not os.path.exists(ans_path):
        # 获取文件下载链接
        detail_text = requests.get(url=detail_url, headers=headers, timeout=300).text
        down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)', detail_text, re.S)[0] # 发现每个页面框架不一样,但是只有一个rar文件
        # 下载并保存rar文件
        data = requests.get(url=down_url, headers=headers, timeout=180).content
        with open(ans_path, 'wb') as fp:
            fp.write(data)
            print(name, '下载成功')
    else:
        print(name, '已存在')

然后开10个线程来执行

from multiprocessing.dummy import Pool

pool = Pool(10)
pool.map(down, li_list)
pool.close()
pool.join()

这样可以提升一些执行成功率

第五难: 最后的BUG

当你下载得差不多的时候, 突然又报错了

IndexError: list index out of range

并且指向了这一行代码:

down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)', detail_text, re.S)[0]

索引已经是0了, 却仍然报这个错误, 说明在某一次执行中正则表达式啥都没匹配到

经过排查找到了罪魁祸首:

ph

那么我们在正则表达式里给他加个特判就解决了

down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)|(http://files\.eduu\.com/down\.php\?id=288283)', detail_text, re.S)[0] # 发现每个页面框架不一样,但是只有一个rar文件, 第14届的要特判

但是这里要注意, 这样解析出的结果是一个列表(其中一项是空的, 一项是下载链接), 因为表达式中有两个捕获组, 如果直接这么写, requests模块无法解析正确的url, 所以要手动判断一下

解决方法为加入下面这段代码

if down_url[0]:
            down_url = down_url[0]
        else:
            down_url = down_url[1]

第六难: 最后的美化

在各个主要的代码前后加入了输出提示

发现由于文件名长度差异大, 直接用\t无法实现对齐, 所以又写了个函数, 就不展开了

源码

最后附上全部源码!

# -*- coding:utf-8 -*-
import requests
from lxml import etree
import os
import re
from multiprocessing.dummy import Pool
import unicodedata

# 创建储存目录
if not os.path.exists('./phy'):
    os.makedirs('./phy')

url = 'http://sh.gaokao.com/gzjs/gewl/gewlst/'
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36 Edg/128.0.0.0'
}

# 创建文件目录实例
response = requests.get(url=url, headers=headers, timeout=120)
response.encoding = 'gb2312'
page_text = response.text
tree = etree.HTML(page_text)

# 输出对齐函数
width = 68
def get_display_width(text):
    width = 0
    for char in text:
        if unicodedata.east_asian_width(char) in 'WF':
            width += 2
        else:
            width += 1
    return width

def format_text(text, width):
    display_width = get_display_width(text)
    padding = width - display_width
    return text + ' ' * padding

# 解析li列表
li_list = tree.xpath('//div[@id="main"]//ul[@class="text_list1"]/li')
def down(li):
    detail_url = li.xpath('./div[@class="title"]/a/@href')[0] # 获取详情页链接
    name = li.xpath('./div[@class="title"]/a/text()')[0]  # 获取文件名
    ans_path = './phy/' + name +'.rar' # 生成文件路径

    if not os.path.exists(ans_path):
        # 获取文件下载链接
        print(f"{format_text(name, width)}\t\033[0;36m\033[1m获取下载链接......\033[0m")
        detail_text = requests.get(url=detail_url, headers=headers, timeout=300).text
        down_url = re.findall('(http://files\.eduuu\.com/ohr/.*?\.rar)|(http://files\.eduu\.com/down\.php\?id=288283)', detail_text, re.S)[0] # 发现每个页面框架不一样,但是只有一个rar文件, 第14届的要特判
        if down_url[0]:
            down_url = down_url[0]
        else:
            down_url = down_url[1]
        print(f"{format_text(name, width)}\t\033[0;32m\033[1m成功获取下载链接!\033[0m")

        # 下载并保存rar文件
        print(f"{format_text(name, width)}\t\033[0;36m\033[1m开始下载......\033[0m")
        data = requests.get(url=down_url, headers=headers, timeout=180).content
        with open(ans_path, 'wb') as fp:
            fp.write(data)
            print(f"{format_text(name, width)}\t\033[0;32m\033[1m下载成功!\033[0m")
    else:
        print(f"{format_text(name, width)}\t\033[0;35m\033[1m已存在!\033[0m")

temp_txt = '\033[0;37m\033[1m文件\033[0m'
print(f"{format_text(temp_txt, 85)}\t\033[0;34m\033[1m状态\033[0m")
pool = Pool(10)
pool.map(down, li_list)

pool.close()
pool.join()

print('\n\033[0;32m\033[1m\033[4m全部下载完成!\033[0m')

结语&注意事项

  1. 由于网站不稳定性, 一次下载很难一次性成功, 可能需要多运行几次, 直到出现全部下载完成提示
  2. 过程中由于网络问题而没能下载出现的报错是requests.exceptions.ChunkedEncodingError: Response ended prematurelyurllib3.exceptions.ProtocolError: Response ended prematurely其他报错得出现是不正常的, 可能是BUG
  3. 本人是新手, 有一些错误或者用词不当的地方欢迎指出

免费评分

参与人数 5吾爱币 +11 热心值 +3 收起 理由
AuroraVerses + 1 我很赞同!
tank59 + 1 + 1 用心讨论,共获提升!
FitContent + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
qiuku + 1 谢谢@Thanks!
侃遍天下无二人 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

 楼主| icer233 发表于 2024-8-25 14:20
本帖最后由 icer233 于 2024-8-25 14:22 编辑
Yifan2007 发表于 2024-8-25 14:06
楼主能打包下不,我用vscode环境老是报错跑不起来

打包版本:https://nbboy.lanzn.com/iJLFV28cdtuf

免费评分

参与人数 1热心值 +1 收起 理由
Yifan2007 + 1 谢谢@Thanks!

查看全部评分

 楼主| icer233 发表于 2024-8-25 14:12
Yifan2007 发表于 2024-8-25 14:06
楼主能打包下不,我用vscode环境老是报错跑不起来

我直接把下好得发你吧这个网站老是连不上挺糟心的
下载好的:https://nbboy.lanzn.com/iFHuZ28cdf3e

免费评分

参与人数 1吾爱币 +1 热心值 +1 收起 理由
Yifan2007 + 1 + 1 我很赞同!

查看全部评分

Yifan2007 发表于 2024-8-25 14:06
楼主能打包下不,我用vscode环境老是报错跑不起来
aa868682008 发表于 2024-8-25 16:05
难得的有心人
justwz 发表于 2024-8-25 20:35
拿走试试 python爬虫真方便
AuroraVerses 发表于 2024-9-2 21:59
绝了,方便
caochanyue 发表于 2024-9-3 18:59
挺有意思的,喜欢看这样的实战贴
 楼主| icer233 发表于 2024-9-19 22:37
判断下载链接的时候其实不用if else,直接把两个string加起来就行
xulous 发表于 2024-9-29 13:52
喜欢看这样的实战贴
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-14 11:00

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表