Python爬取教学网站资源文件

in 代码技巧 with 0 comment

实训要到了,老师说资料在教学网站上,好吧,我去看了看。

1568539145632.png
文件要一个一个下载,不能打包,哈哈哈哈哈,所以就有了这篇文章。

既能愉快的下载文件,也能锻炼一下Python技能,一举两得。

利用requests库的get方法下载文件

方便之后调用,首先把下载文件的东西写好:

import os
import requests
from bs4 import BeautifulSoup


def save_file_stream(path, file_url):
    try:
        r = requests.get(file_url, timeout=3, stream=True)
        with open(path, "wb") as f:
            for chunk in r.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
        print("File is saved.")
    except requests.exceptions.ConnectionError:
        print("File save failed.")
当使用requests的get下载大文件/数据时,建议使用stream模式。

当把get函数的stream参数设置成False时,它会立即开始下载文件并放到内存中,如果文件过大,有可能导致内存不足。

当把get函数的stream参数设置成True时,它不会立即开始下载,当你使用iter_content或iter_lines遍历内容或访问内容属性时才开始下载。

需要注意一点:文件没有下载之前,它也需要保持连接。

iter_content:一块一块的遍历要下载的内容
iter_lines:一行一行的遍历要下载的内容
使用上面两个函数下载大文件可以防止占用过多的内存,因为每次只下载小部分数据。

下面这个函数是我在测试的时候,用来控制是否覆盖下载文件的,可有可无:

def save_file(path, file_url, cover=False):
    print("Saving file:" + path)
    if os.path.exists(path):
        if cover:
            print("File is covered.")
            save_file_stream(path, file_url)
        else:
            print("File is ignored.")
    else:
        save_file_stream(path, file_url)

遍历课程资源

遍历网页上所有的资源,判断是否是文件,如果是就调用之前的下载工具进行下载。

判断方法也很简单,对于列表,URL请求是listview.jsp?key=value*,而文件的请求是preview/download_preview.jsp?key=value*,只需要对URL进行分割判断就可以了。

1568539409517.png

1568539474572.png

遍历的方法是通过bs4中的BeautifulSoup库解析HTML,获取无序列表每一项的链接,遇到文件夹则执行递归。

def get_all_files(uri, path):
    # 分割URL
    action = uri.split("?", 1)[0]
    if action == "preview/download_preview.jsp":
        r = requests.get("http://e.njcit.cn/meol/common/script/" + uri)
        # BeautifulSoup解析HTML
        soup = BeautifulSoup(r.text, "html.parser")
        # 提取文件的URL
        lists = soup.select("div#dowload-preview div.h1-title h2 p a")
        if len(lists) > 0:
            # 调用之前的方法下载文件
            file_url = "http://e.njcit.cn/" + lists[0].attrs["href"]
            save_file(path, file_url, cover=True)
        return
    if action == "listview.jsp":
        # 对于不存在的文件夹进行创建
        if os.path.exists(path) is False:
            print("Create path:", path)
            os.makedirs(path)
        r = requests.get("http://e.njcit.cn/meol/common/script/" + uri)
        # BeautifulSoup解析HTML
        soup = BeautifulSoup(r.text, "html.parser")
        # 提取无序列表的每一项
        lists = soup.select("form#postform table.valuelist tr td.indentten a")
        for e in lists:
            # 执行递归
            get_all_files(e.attrs["href"], path + "/" + e.text)
    else:
        return

至此我以为已经可以运行程序,来愉快的下载文件了

if __name__ == '__main__':
    # 需要下载的文件夹
    list_index = "listview.jsp?acttype=enter&folderid=146039&lid=22446"
    # 文件保存位置
    save_path = "."
    # 调用递归下载
    get_all_files(list_index, save_path)

但是运行后发现下载的文件全是

1568539599340.png

What the hell?

我试着用文本编辑器打开,发现原来是一个验证码界面。

1568539658107.png

很奇怪我在网页下载的时候并不需要验证码,这里为什么又需要了呢?

进一步观察发现,我获取到的文件列表里没有“书籍”文件夹。What?

我复制了浏览器里的链接,写了几行代码,只下载书籍页面。

import requests
r = requests.get("http://e.njcit.cn/meol/common/script/listview.jsp?lid=22446&folderid=146403")
with open("1.html", "w") as f:
    f.write(r.text)

可以发现是权限不足的缘故,要登录!

1568539829462.png

模拟浏览器登录并保持会话

关于cookie和session可以自行搜索,我这里直接贴代码

import requests.cookies
from bs4 import BeautifulSoup

# 登录请求地址
login_url = "http://e.njcit.cn/meol/loginCheck.do"
# POST提交的数据
post_data = {
    "logintoken": "1568372860156",  # 随机数这里不研究
    "IPT_LOGINUSERNAME": "1704301**",  # 对应学号
    "IPT_LOGINPASSWORD": "********"  # 对应密码
}
# HTTP请求头
header = {
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0"
}
session = requests.session()  # 创建会话
session.headers = header
session.post(login_url, post_data)  # 模拟登录
res = session.get("http://e.njcit.cn/meol/personal.do")
soup = BeautifulSoup(res.text,  "html.parser")
# 提取当前登录用户的用户名,以此判断是否登录成功
name_tag = soup.select("div.sidebar div.userinfobody ul li.name div a.info.chgright")
if name_tag:
    print("当前登录:", name_tag[0].text)
    print(session.cookies)
    # 这里用来保持会话,防止cookie失效
    while True:
        time.sleep(1000)
else:
    print("登录失败")

这亚子应该没有问题了,最终代码先模拟登录,保持会话,之后的请求全都是通过这个会话来发送的,需要将之前代码中的requests替换成我们创建的session,最终代码如下

#!/usr/bin/env python
# -*-coding:utf-8-*-
# 
# @Project  : NJCIT
# @FileName : download_all.py
# @Author   : XueYe
# @Date     : 2019/9/13 16:25
# @Desc     : Life is Short I Use Python!

import os
import requests
from bs4 import BeautifulSoup


def save_file_stream(path, file_url):
    try:
        r = session.get(file_url, timeout=3, stream=True)
        with open(path, "wb") as f:
            for chunk in r.iter_content(chunk_size=1024):
                if chunk:
                    f.write(chunk)
        print("File is saved.")
    except requests.exceptions.ConnectionError:
        print("File save failed.")


def save_file(path, file_url, cover=False):
    print("Saving file:" + path)
    if os.path.exists(path):
        if cover:
            print("File is covered.")
            save_file_stream(path, file_url)
        else:
            print("File is ignored.")
    else:
        save_file_stream(path, file_url)

        
def get_all_files(uri, path):
    action = uri.split("?", 1)[0]
    if action == "preview/download_preview.jsp":
        r = session.get("http://e.njcit.cn/meol/common/script/" + uri)
        soup = BeautifulSoup(r.text, "html.parser")
        lists = soup.select("div#dowload-preview div.h1-title h2 p a")
        if len(lists) > 0:
            file_url = "http://e.njcit.cn" + lists[0].attrs["href"]
            save_file(path, file_url, cover=True)
        return
    if action == "listview.jsp":
        if os.path.exists(path) is False:
            print("Create path:", path)
            os.makedirs(path)
        r = session.get("http://e.njcit.cn/meol/common/script/" + uri)
        soup = BeautifulSoup(r.text, "html.parser")
        lists = soup.select("form#postform table.valuelist tr td.indentten a")
        for e in lists:
            get_all_files(e.attrs["href"], path + "/" + e.text)
    else:
        return


if __name__ == '__main__':
    login_url = "http://e.njcit.cn/meol/loginCheck.do"
    post_data = {
        "logintoken": "1568372860156",
        "IPT_LOGINUSERNAME": "1704301**",  # 学号
        "IPT_LOGINPASSWORD": "********"  # 密码
    }
    header = {
        "user-agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; rv:53.0) Gecko/20100101 Firefox/53.0"
    }
    session = requests.session()
    session.headers = header
    session.post(login_url, post_data)
    res = session.get("http://e.njcit.cn/meol/personal.do")
    soup = BeautifulSoup(res.text, "html.parser")
    name_tag = soup.select("div.sidebar div.userinfobody ul li.name div a.info.chgright")
    if name_tag:
        print("当前登录:", name_tag[0].text)
        list_index = "listview.jsp?acttype=enter&folderid=146039&lid=22446"
        save_path = "."
        get_all_files(list_index, save_path)
        # 下载完成后退出账户,我测试的时候因为频繁登录发现对用户最大在线数量有限制
        logout_url = "http://e.njcit.cn/meol/homepage/V8/include/logout.jsp"
        session.get(logout_url)
        print("用户已退出")
    else:
        print("登录失败")

终于舒服了,开始下载去了,不知道学校小水管要多久,下完了回来更新一下。

1568544656409.png

Responses