Python网络爬虫项目化教学


声明:网站根目录下的robots.txt一般写了爬虫的协议,请遵守道德,不要乱爬,出了事别说是我教的。

基础知识部分

一、三国演义下载(request)

import requests 

url = "http://10.163.72.39:7777/image/sanguo1.md"
res = requests.get(url) 
print(res.status_code) #打印对象的响应状态码,以检查请求是否成功
print(res.text)        #把对象的文本以字符串的形式返回

# 创建一个名为三国演义的txt文档,以写模式打开
file = open("三国演义.txt","w")
# 写进文件中,相当于——  蔡徐坤.吃饭("包子")
file.write(res.text)
# 关闭文档,相当于——    蔡徐坤.睡觉()  
file.close()  
测验:批量爬取三国演义前三章,分别为sanguo2.md和sanguo3.md

二、爬取一张图片(request)

import requests
url = 'http://10.163.72.39/img/angry.55b2c9de.gif'
res = requests.get(url) 
print(res.status_code)    #打印对象的响应状态码,以检查请求是否成功
print(res.content)        #打印对象的二进制编码内容

# 新建了一个文件angry.jpg,这里的文件没加路径,它会被保存在程序运行的当前目录下。
# 图片内容需要以二进制wb读写。
photo = open('angry.jpg','wb')
# 获取pic的二进制内容
photo.write(res.content) 
# 关闭文件
photo.close()

练习:我网站的所有图片,自己爬着玩吧

测验:天堂图片网批量爬取,任务要求,批量爬取从高渐离到孙悟空的十几张图片,其中有一张图片被我删除了,在爬取的时候请跳过。

http://10.163.72.39/%E6%B2%99%E6%BA%A2%E5%A3%81%E7%BA%B8%E5%9B%BE%E7%89%87_212%E5%BC%A0%E9%AB%98%E6%B8%85%E5%9B%BE%E7%89%87%20(%E5%A4%A9%E5%A0%82%E5%9B%BE%E7%89%87%E7%BD%91).html

三、爬取一段视频(request)

import requests 
url = 'http://10.163.72.39/media/banner3.043818a0.mp4'
res = requests.get(url)
print(res.status_code)    #打印对象的响应状态码,以检查请求是否成功
print(res.content)        #打印对象的二进制编码内容

# 新建了一个文件banner.mp4,这里的文件没加路径,它会被保存在程序运行的当前目录下。
# 图片内容需要以二进制wb读写。
video = open('banner.mp4','wb')
# 获取pic的二进制内容
video.write(res.content) 
# 关闭文件
video.close()

四、豆瓣电影top250爬虫(静态网页bs4)

http://10.163.72.39/%E8%B1%86%E7%93%A3%E7%94%B5%E5%BD%B1%20Top%20250.html

import requests,openpyxl
from bs4 import BeautifulSoup

wb=openpyxl.Workbook()
sheet=wb.active
sheet.title='豆瓣'
sheet['A1']='电影名'
sheet['B1']='评分'
sheet['C1']='推荐语'
sheet['D1']='链接'

for i in range(10):
    url='http://10.163.72.39/%E8%B1%86%E7%93%A3%E7%94%B5%E5%BD%B1%20Top%20250.html?start='+str(i)+'&filter='
    # url='https://movie.douban.com/top250?start='+str(i)+'&filter='
    headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
    res=requests.get(url,headers=headers)
    soup=BeautifulSoup(res.text,'html.parser')
    movies=soup.find_all(class_="info")
    for movie in movies:
        href=movie.find('a')['href']
        name=movie.find(class_="title").text
        star=movie.find(class_="rating_num").text
        quote=movie.find(class_="quote").text
        sheet.append([name,star,quote,href])
        print(href+name+star+quote)

wb.save('.\豆瓣爬虫的数据.xlsx')

五、机房管理系统爬虫(静态网页bs4)

爬取教师姓名的缩写,你们懂的,这个姓名缩写同时也是ftp的账号,可以用来爆破ftp的密码。

import requests,re
from bs4 import BeautifulSoup

txt=open('./teacher_name.txt','a')

url='http://10.163.72.11/index/admin/teacher'
headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
res=requests.get(url,headers=headers)
soup=BeautifulSoup(res.text,'html.parser')
items = soup.find_all('h5',class_='card-title')
for item in items:
    pattern = re.compile(r'【(.*?)】')
    name = re.findall(pattern,item.text)[0]
    print(name)
    txt.write(name + 'down' + '\n')

txt.close()

六、爬取神秘暗网留言板(动态网站json)

import requests,openpyxl

wb = openpyxl.Workbook()
sheet = wb.active
sheet.title = '留言数据'
sheet['A1'] = '用户ID'
sheet['B1'] = '用户名'
sheet['C1'] = '积分'
sheet['D1'] = '留言内容'

# 爬取前十页
for i in range(1,10):
    url = 'http://10.163.72.55/messageData.php?currentPage='+str(i)
    res = requests.get(url)

    res_jons = res.json()  # 解析为json格式数据
    print(type(res_jons))

    massage_list = res_jons['data']
    for item in massage_list:
        id = item['id']
        username = item['username']
        rankScore = item['rankScore']
        message = item['message']
        sheet.append([id,username,rankScore,message])

wb.save('.\神秘暗网留言板爬虫.xlsx')

七、带着参数请求数据(偷看隐藏数据)

假设神秘暗网首页作业栏目,不可以点击查看更多,可以携带参数查看隐藏信息。

import requests

url = 'http://10.163.72.55/taskListData.php'
params = {'task_num':3}
res = requests.get(url,params=params)
res_json = res.json()
task_list = res_json['data']

for task in task_list:
    print('作业标题为:',task['title'])

假设用户管理中,教师组属于敏感信息,不让普通用户查询,你知道该怎么做了吧。

import requests

url = 'http://10.163.72.55/userAdminDataClass.php'
params = {'className':'22计2'}
res = requests.get(url,params=params)
res_json = res.json()
user_list = res_json['data']

for user in user_list:
    print('用户姓名为:',user['username'])

八、指挥浏览器登录神秘暗网(自动脚本)

from selenium import  webdriver # 从selenium库中调用webdriver模块
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
import time

# chrome_options = Options()
# chrome_options.add_argument('--no-sandbox')  # 可选项,用于在 Linux 上启动 Chrome
# chrome_options.add_argument('--disable-dev-shm-usage')  # 可选项,用于在 Linux 上启动 Chrome
# chrome_options.add_argument('--headless')  # 可选项,无头模式
# chrome_options.add_argument('--disable-gpu')  # 可选项,禁用 GPU
# driver = webdriver.Chrome(options=chrome_options)
driver=webdriver.Chrome()
driver.get('http://10.163.72.55/#/index/IndexTask') # 访问页面
time.sleep(2) # 暂停两秒,等待浏览器缓冲

login = driver.find_element(By.ID,'login')
login.click() # 点击【提交】按钮

username = driver.find_element(By.ID,'username') # 找到用户名输入框位置
username.send_keys('你的名字') # 输入文字
password = driver.find_element(By.ID,'password') # 找到密码输入框位置
password.send_keys(123456)
login_button = driver.find_element(By.CLASS_NAME,'login_button') # 找到密码输入框位置
login_button.click()

九、通过cookie留言(突破留言板限制)

import requests
#获得cookies
url='http://10.163.72.55/LoginPost.php'
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'}
data={
    'username': 'username',
    'password': 'password',
    }
login=requests.post(url,headers=headers,data=data)
cookies=login.cookies
print(cookies)

#用cookies登陆
url_2='http://10.163.72.55/messagePost.php'
params = {'gb_word':'没想到吧,我还是可以留言,哈哈'}
res=requests.get(url_2,cookies=cookies,headers=headers,params=params)
res_json = res.json()

print(res_json)

十、使用scrpit框架爬豆瓣

pip install scrapy -i https://pypi.tuna.tsinghua.edu.cn/simple/ # 安装环境
scrapy startproject douban # 创建项目
scrapy crawl douban #运行工程

爬虫模块(在spiders目录下新建xx.py文件)

# (爬虫)部门是公司的核心业务部门
# 负责创建requests对象和接受引擎发送过来的response(Downloader部门爬取到的内容),从中解析并提取出有用的数据。
# 它对应的是爬虫流程【解析数据】和【提取数据】这两步
import scrapy
import bs4
# 需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。
from ..items import DoubanItem
# DoubanSpider类继承自scrapy.Spider类
class DoubanSpider(scrapy.Spider):
    name = 'douban'   # 定义爬虫的名字,这个名字是爬虫的唯一标识
    # allowed_domains是定义允许爬虫爬取的网址域名(不需要加https://)。
    # 如果网址的域名不在这个列表里,就会被过滤掉
    # 一定在book.douban.com这个域名之下,防止爬到广告页面去
    allowed_domains = ['book.douban.com']
    # 定义起始网址,就是爬虫从哪个网址开始抓取
    # 注:allowed_domains的设定对start_urls里的网址不会有影响
    start_urls = ['https://book.douban.com/top250?start=0']

    # 根据url的规律,构建出前三页地址
    start_urls = []
    for x in range(3):
        url = 'https://book.douban.com/top250?start=' + str(x * 25)
        start_urls.append(url)
    
    # 解析下载器传过来的数据
    def parse(self, response):
        # 爬取的数据存储在response.text
        bs = bs4.BeautifulSoup(response.text,'html.parser')
        datas = bs.find_all('tr',class_="item")
        for data in  datas:
            #实例化DoubanItem类。
            item = DoubanItem()
            # 书名
            item['title'] = data.find_all('a')[1]['title']
            #提取出出版信息,并把这个数据放回DoubanItem类的publish里。
            item['publish'] = data.find('p',class_='pl').text
            # 评分
            item['score'] = data.find('span',class_='rating_nums').text
            print(item['title'])
            #yield item是把获得的item传递给引擎。
            #有点类似return,不过它和return不同的点在于,它不会结束函数,且能多次返回信息
            yield item

数据处理模块(items.py)

# (数据管道)部门则是公司的数据部门
# 只负责存储和处理Spiders部门提取到的有用数据。这个对应的是爬虫流程【存储数据】这一步。
import scrapy

#定义一个类DoubanItem,它继承自scrapy.Item
class DoubanItem(scrapy.Item):
    # scrapy.Field() 相当于自定义字典类型
    title = scrapy.Field() #定义书名的数据属性
    publish = scrapy.Field() #定义出版信息的数据属性
    score = scrapy.Field() #定义评分的数据属性

设置(settings.py)

# 保存爬取的数据
FEED_URI='./storage/data/%(name)s.csv'
FEED_FORMAT='csv'
FEED_EXPORT_ENCODING='ansi' # 导出文件编码,ansi是一种在windows上的编码格式

USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
# 是否需要遵守爬虫规则,改成不遵守
ROBOTSTXT_OBEY = False
# 爬虫的速度
DOWNLOAD_DELAY = 0.5

主程序启动模块 (main.py,和scrapy.cfg同级)(注意:一定要将项目文件夹作为编程文件夹)

#导入cmdline模块,可以实现控制终端命令行。
from scrapy import cmdline
#用execute()方法,输入运行scrapy的命令。
cmdline.execute(['scrapy','crawl','douban'])

作业:爬取当当网前三页,书名,作者,价格
http://bang.dangdang.com/books/bestsellers/01.00.00.00.00.00-year-2018-0-1-1

扩展提升
爬取每一条新闻里的内容
http://www.news.zjut.edu.cn/5415/list.htm

# (爬虫)部门是公司的核心业务部门
# 负责创建requests对象和接受引擎发送过来的response(Downloader部门爬取到的内容),从中解析并提取出有用的数据。
# 它对应的是爬虫流程【解析数据】和【提取数据】这两步
import scrapy
import bs4
# 需要引用DoubanItem,它在items里面。因为是items在top250.py的上一级目录,所以要用..items,这是一个固定用法。
from ..items import DoubanItem

# DoubanSpider类继承自scrapy.Spider类
class DoubanSpider(scrapy.Spider):
    name = 'douban'   # 定义爬虫的名字,这个名字是爬虫的唯一标识
    # allowed_domains是定义允许爬虫爬取的网址域名(不需要加https://)。
    # 如果网址的域名不在这个列表里,就会被过滤掉
    # 一定在book.douban.com这个域名之下,防止爬到广告页面去
    allowed_domains = ['www.news.zjut.edu.cn']
    # 定义起始网址,就是爬虫从哪个网址开始抓取
    # 注:allowed_domains的设定对start_urls里的网址不会有影响
    start_urls = ['http://www.news.zjut.edu.cn/5415/list.htm']
    
    # 解析下载器传过来的数据
    def parse(self, response):
        # 爬取的数据存储在response.text
        bs = bs4.BeautifulSoup(response.text,'html.parser')
        datas = bs.find_all('span',class_="news_title")
        for data in datas:
            href = data.find('a')['href']
            link = 'http://www.news.zjut.edu.cn/5415/list.htm' + href
            # 链接先爬下来回传给引擎,引擎通过调度器给下载器,下载好了再传给我
            yield scrapy.Request(link, callback=self.parse2)
    
    #这个就是下载器下载好的数据
    def parse2(self, response):
        bs = bs4.BeautifulSoup(response.text,'html.parser')
        item = DoubanItem()
        item['title'] = bs.find(class_='arti_title').text
        yield item

十一、破解并下载视频流

import requests,re,random
from Crypto.Cipher import AES

# 伪装请求头
user_agent = [
    'Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30',
    'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)',
    'Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.9.168 Version/11.50',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
]
headers = {'User-Agent': random.choice(user_agent)}

url = 'http://www.nvic.edu.cn/CompetitionVideo/Detail?worksId=3ddcd2d0-39f1-49b8-83a2-03b8e5ef780d&sType=5&competitionId=ce116ee4-ca64-4c16-9aff-750fed3335cf'
res = requests.get(url,headers=headers)
# 获取几个独立视频的链接
pattern=re.compile('data-path="(.*?)"')
indexs_list=re.findall(pattern,res.text)
for index in indexs_list:
    # 获取视频文件存放路径
    path = index[:-10]
    res = requests.get(index,headers=headers)
    pattern=re.compile('URI="(.*?.key)"')
    key= re.findall(pattern,res.text)[0] 
    key_url = path + key  # 拼凑出key的地址
    print(key_url)
    key_res = requests.get(key_url) # 拿到key的内容
    print('已获取到key')
    print(key_res.content)
    aes = AES.new(key_res.content, AES.MODE_CBC, b'0000000000000000')  # 制作解密文件
    print('已成功制作解密文件')
    # 拿视频碎片
    pattern=re.compile('(filesequence\d+.ts)')
    filesequences_list=re.findall(pattern,res.text)
    count = 1 # 计数、缓解等待压力
    for filesequence in filesequences_list:
        filesequence_url = path + filesequence
        res = requests.get(filesequence_url,headers=headers)
        # 图片内容需要以二进制wb读写。
        video = open(f'{key}.mp4','ab+')
        # 获取pic的二进制内容
        video.write(aes.decrypt(res.content)) 
        # 关闭文件
        video.close()
        count += 1
        print("\r[下载进度]:{}{:.2f}%".format('>'*count,count/len(filesequences_list)*100),end='')

十二、新遇到的一点反爬问题

最近爬这个网站遇到一点问题,https://www.ly.com/hotel/hotellist?pageSize=20&t=1693794571107&city=321&inDate=2023-09-04&outDate=2023-09-05&filterList=8888_1

以前可以爬,现在不能爬,经过研究,发现是网站开发者增加了对请求头字段的校验。

import requests

headers_str = '''accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
accept-language: zh-cn
appfrom: 16
cluster: idc
Connection: keep-alive
Cookie: soso_17u_tab_open_index=1; H5CookieId=2a31035b-81b0-452f-8af5-4bc2cb6d6229; firsttime=1693790207090; abtkey=037eed29-b152-4fc3-92a8-027faa2dffc6; _tcudid_v2=GVAglBHWXx1wFDh4_P4gvwe79p_DO7Vf2Yj7sQ9vCd4; nus=userid=682242716&nickName=%e5%90%8c%e7%a8%8b%e4%bc%9a%e5%91%98_0D043E7512A&level=1; __tctma=144323752.1693790169808694.1693790169437.1693790169437.1693793067529.2; hotel_lang=zh-cn; 17uCNRefId=RefId=0&SEFrom=&SEKeyWords=; TicketSEInfo=RefId=0&SEFrom=&SEKeyWords=; CNSEInfo=RefId=0&tcbdkeyid=&SEFrom=&SEKeyWords=&RefUrl=; qdid=-9999; businessLine=hotel; H5Channel=mnoreferseo%2CSEO; indate=2023-09-09; outdate=2023-09-10; route=e83eaebd8f07fc1b8cfab528aeb2900e; lasttime=1694239332948; JSESSIONID=A23D8BD953AE131A1233B6F406759B2E
deviceid: 2a31035b-81b0-452f-8af5-4bc2cb6d6229
Host: www.ly.com
Referer: https://www.ly.com/hotel/hotellist?pageSize=20&t=1694239332807&city=321&inDate=2023-09-09&outDate=2023-09-10&filterList=8888_1
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
timezone: 8
tmapi-client: tpc
traceid: d6d36af2-7f3a-4504-a125-db1cfa470c97
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36'''

headers_dict = {}
line_list = headers_str.split('\n')
for line in line_list:
    key = line.split(':')[0]
    value = line.split(':')[1]
    headers_dict[key] = value.strip()
# print(headers_dict)

url = 'https://www.ly.com/tapi/v2/list?pageSize=20&t=1694239311116&city=321&inDate=2023-09-09&outDate=2023-09-10&filterList=8888_1&pageIndex=0&sugActInfo='
res = requests.get(url,headers=headers_dict)
res_json = res.json()
hotel_list = res_json['data']['hotelList']
for hotel in hotel_list:
    hotelName = hotel['hotelName']
    print(hotelName)

项目一:消息通知器

一、制作背景

如果要应聘某个单位,但是不知道这个单位什么时候发布招聘信息,总不能天天上官网去看吧,对于我这样孤僻的性格,消息闭塞,很有可能错过重要的招聘信息。
因此,我需要一个程序可以自动爬取网站的消息栏,如果发现有自己关注的信息,则发一封邮件给自己。
例如海盐商贸学校发布了一条招聘计算机教师的通知,我需要第一时间得到通知。

二、代码分享

import requests
import smtplib
import schedule
import time,os
from bs4 import BeautifulSoup
from email.mime.text import MIMEText
from email.header import Header
 
def account_save(keyword,qqmail):
#----保存用户邮箱和关键词信息----#
    with open(account_path,'w') as f:
        f.writelines([keyword+'\n',qqmail])
        f.close()
 
def account_load():
#----读取邮箱和关键词信息----#
    with open(account_path,'r') as f:
        f_content=f.readlines()
        return f_content[0][:-1],f_content[1]
 
def YanGongBu_spider(keyword):
#----爬取研工部通知栏前三项----#
    headers={'user-agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
    url='http://yjsb.zjnu.edu.cn/'
    res=requests.get(url,headers=headers)
    soup=BeautifulSoup(res.text,'html.parser')
    titles=soup.find_all('span',class_="meta-title")
    for title in titles[10:13]:
        if keyword in title.text:
            send_email(title.text,qqmail)
 
def send_email(title,qqmail):
#----使用710464440代发邮件----#
    account=qqmail
    password='这里填写邮箱密码'
    reveiver='710464440@qq.com'
    mailhost='smtp.qq.com'
    qqmail=smtplib.SMTP()
    qqmail.connect(mailhost,25)
    qqmail.login(account,password)
    content=title
    massage=MIMEText(content,"plain","utf-8")
    subject='研工部通知'
    massage['Subject']=Header(subject,'utf-8')
    try:
        qqmail.sendmail(account,reveiver,massage.as_string())
        print('邮件发送成功')
    except:
        print('邮件发送失败')
    qqmail.quit()
 
if __name__=='__main__':
    base_dir = os.path.dirname(__file__)  #获取当前文件夹的绝对路径
    account_path=base_dir+"/account.txt"
    print(account_path)
    if not os.path.exists(account_path):
        keyword=input('请输入你想关注的内容的关键词(例如:招聘):')
        qqmail=input("请输入你的qq邮箱地址(例如:715555555@qq.com):")
        account_save(keyword,qqmail)
    else:
        keyword,qqmail=account_load()
    YanGongBu_spider(keyword)
    input('按任意键退出程序')
 
#这段代码用于挂远程服务器
#schedule.every().day.at('17:30').do(YanGongBu_spider)
#while True:
    #schedule.run_pending()
    #time.sleep(1)

三、使用小tip

如果是本地使用的话,利用windows的任务计划程序,每天定时运行程序,如果那个时间电脑没开,程序则不会运行,而且如果在玩游戏,可能会被黑窗口弹一下,比较影响体验。
因此,我们需要将代码挂在远程的服务器上,服务器24小时运行,完美解决问题。

项目二:网课自动刷点赞刷评论刷积分

疫情期间上网课,学校使用UMN教学平台进行授课,点赞和评论都可以获得积分,积分影响最终成绩,别的同学都是手动弄,我觉得太麻烦,所以我打算用Python刷一刷。

#2.2版本公告:新增账号保存功能,用户只需要输入一次账号密码,会自动以文本格式保存在当前文件夹下,第二次运行会自动读取
#2.1版本公告:程序通过相对路径找到chromedriver.exe,不需要用户再手动填写绝对路径了
#2.0版本公告:加入自动评分和评论的功能,加入可显示剩余人数的功能
#1.7版本公告:做了代码优化,并封装了部分复用的代码
#1.6版本公告:加入了用户自定义输入,可自定义评论内容
#1.5版本公告:修复了因找不到chromedriver而造成的'chromedriver' executable needs  to be in PATH的bug
#1.4版本公告:修复因跳过页面展开而造成的实际人数统计的错误,加入了页面滑动以及显性等待机制,最长等待20秒
#1.3版本公告:修复一处因服务器排队而造成的程序无法运行的bug,加入了3分钟的页面隐性等待
#1.2版本公告:修复一处在展开页面时,因为网络不稳定而造成的list index out of range的bug,加入了异常跳过机制
#1.1版本公告:修复一处因页面返回而导致的实际点赞人数不足的bug,在页面返回时需重新展开所有作业名单
#unm自动点赞器1.0功能介绍:用于在unm平台实现对其他同学作业的自动点赞。

import time,pyautogui,openpyxl,os
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def look_all_task():
#----展开所有作业名单----#
    try:
        for i in range(2):
            pyautogui.scroll(-10000)
            more = WebDriverWait(driver,20).until(EC.presence_of_element_located((By.CLASS_NAME,"btn-inner")))
            driver.execute_script("arguments[0].click();",more)
            time.sleep(2)
    except IndexError:
        print('跳过因为网络不稳定展开页面失败而造成的list index out of range问题')

def account_save(user_acount,user_password):
#----保存用户账号密码信息----#
    with open(account_path,'w') as f:
        f.writelines([user_acount+'\n',user_password])
        f.close()

def account_load():
#----读取用户账号密码信息----#
    with open(account_path,'r') as f:
        f_content=f.readlines()
        return f_content[0],f_content[1]

if __name__=='__main__':
    #----用户自定义输入----#
    base_dir = os.path.dirname(__file__)  #获取当前文件夹的绝对路径
    account_path=base_dir+"//account.txt"
    user_comment=input('请输入你要评论的内容,(样例:你的作业做得真好,非常优秀):')
    if not os.path.exists(account_path):
        user_acount=input('请输入你的umn账号:')
        user_password=input('请输入你的umn密码:')
        account_save(user_acount,user_password)
    else:
        user_acount,user_password=account_load()
    #----登录网站----#
    url_task='https://m.umu.cn/session/exercise/?surl=1SqbQ623d'
    url='https://www.umu.cn/index#/index'
    driver=webdriver.Chrome(executable_path='chromedriver.exe')
    # driver=webdriver.Chrome()
    driver.get(url)
    driver.implicitly_wait(180) #隐式等待,最长等待180秒
    account=driver.find_elements_by_class_name('input-text')
    account[0].send_keys(user_acount)
    account[1].send_keys(user_password)
    button=driver.find_element_by_class_name('btn-warning')
    button.click()
    time.sleep(2)
    #----转到作业二的界面----#
    driver.get(url_task)
    time.sleep(4)
    look_all_task()
    #----统计任务数----#
    look_task=driver.find_elements_by_class_name('review-status')
    surplus=len(look_task)
    #----进入个人作业详情页----#
    for j in range(50):
        look_task=driver.find_elements_by_class_name('review-status')
        driver.execute_script("arguments[0].click();",look_task[j+1])
        time.sleep(4)
        driver.implicitly_wait(10)
        #----点赞----#
        try:
            like=driver.find_element_by_class_name('liked-user-prise')
            driver.execute_script("arguments[0].click();",like)
            print('点赞成功')
            time.sleep(2)
        except:
            print('没有找到点赞功能')
        #----评论打分----#
        try:
            comment_button = driver.find_element_by_class_name('do-score')
            pyautogui.scroll(-1000)
            time.sleep(2)
            comment_button.click()
            time.sleep(2)
            score=driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div/div/div/div[2]/div/div/div/div[2]/div[1]/div/input')
            score.send_keys('100')
            comment=driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div/div/div/div[2]/div/div/div/div[3]/div/textarea')
            comment.send_keys(user_comment)
            time.sleep(1)
            submit=driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div/div/div/div[1]/div/span[2]/span')
            submit.click()
            print('评论成功')
            time.sleep(2)
        except:
            print('读取标签失败')
        surplus-=1
        print('剩余',surplus)
        #----转到作业二的界面并展开作业列表----#
        driver.get(url_task)
        time.sleep(2)
        driver.implicitly_wait(10) 
        look_all_task()

总结:这种程序实际上是模仿浏览器进行操作,很难防范,可造成的危害也比较有限,纯属娱乐性质。

项目三:爬取umn网站的所有视频

一、网站分析

打开课程详情页,目标是爬取所有的教学视频。
在详情页抓包,可以得到前三个视频的链接后缀。
第一个视频:1581601804.0237.24958.6125399.mp4
第二个视频:1581605929.0875.63457.6129383.mp4
第三个视频:1581605996.445.72069.6126469.mp4
那这个后缀怎么得到呢,首页的源代码里面是没有的,只能抓包看看,果然发现一个很萌的东西,因为开发者为了让首页加载出封面的图片,而这个图片又是直接从视频截取的,然后顺理成章的,网站开发者为了图方便省事,直接就用了和视频一样的url,只是后缀换成了jpg而已。
既然这样就很简单了,渲染出整个页面的数据,然后取出视频的后缀,拼接一下就行了,代码如下。

二、完整代码

#2.1版本公告:从程序目录启动谷歌浏览器驱动
#2.0版本公告:下载内容从网站动态获得
#1.3版本公告:显示已找到多少个资源
#1.2版本公告:加入了文本进度条显示
#1.1版本公告:新增自动创建文件夹功能,文件检索功能,文件已存在时不会重复下载
#umn一键视频爬取器1.0版本介绍:用于爬取umn教学平台上的的教学视频
import time
import pyautogui
import re
import requests
import os
import time
from selenium import webdriver
from selenium.webdriver.chrome.options import Options

def video_url_get(url_course):
#----获取视频的url----#
    video_list=[]
    #----设置浏览器静默模式(已停用,否则无法下滑鼠标)----#
    chrome_options=Options()
    chrome_options.add_argument('--headless')
    diver=webdriver.Chrome(executable_path='chromedriver.exe')
    #----访问到首页----#  
    diver.get(url_course)
    time.sleep(4)
    #----鼠标往下滑两次----#
    for i in range(5):
        pyautogui.scroll(-10000)
        time.sleep(2)
    #----获取整个页面的数据,提取出视频后缀----#
    Pagesource=diver.page_source
    video_tails=re.compile('(\d*\.\d*\.\d*.{0,10}).mp4',re.S).findall(Pagesource)
    diver.close()
    for video_tail in video_tails:
        video_url='https://statics0.umustatic.cn/videoweike/teacher/weike/4kXW2806/transcoding/'+video_tail+'.mp4'
        video_list.append(video_url)
    return video_list

def downloader(url):
#----下载器----#
    start=time.time()
    size=0 #定义已下载文件的大小(byte)
    chunk_size=1024 #定义每次下载的数据大小(byte)
    root='d://umn_spider_video//'
    path=root+url.split('/')[-1]
    try:
        if not os.path.exists(root):
            os.mkdir(root)
        if not os.path.exists(path):
            res=requests.get(url,stream=True)
            content_size=int(res.headers['content-length'])
            if res.status_code==200:
                print('[文件大小]:%0.2f MB'%(content_size/1024/1024)) #将byte换算成MB
                print("急速爬虫器正在疯狂下载!!!".center(50,'-'))            
                with open(path,'wb') as f:
                    for data in res.iter_content(chunk_size=chunk_size):
                        f.write(data)
                        size+=len(data)
                        print("\r[下载进度]:{}{:.2f}%".format('>'*int(size*50/content_size),size*100/content_size),end='')
                    print('\n下载成功,文件自动帮您保存在d:/umn_spider_video/')
        else:
            print('文件已存在')
    except:
        print('爬取失败')
if __name__=='__main__':
    url_course=input('请输入课程首页的网址,如果不清楚请看教程:')
    # url_course='https://m.umu.cn/course/?groupId=4138993&sKey=0e6490a0d5ef477593326d2e8cd53faa&from_type=myparticipate'#专业教学设计与案例分析
    # url_course='https://m.umu.cn/course/?groupId=5001078&sKey=e30c05f1c34302d77478bf49a21daec2&from_type=myparticipate'  #职业教育德育研究
    video_list=video_url_get(url_course)
    print('已找到资源共%d个'%len(video_list))
    for url in video_list:
        downloader(url)
    input('任务全部完成,欢迎您的再次使用,按任意键退出。')

有了这个程序模板,只要把url_course换成你自己的课程url,就可以爬取到全部的视频,按理说应该可以爬取任意的课程视频了,我又用这个程序取爬取了另外一门课程的视频,实测是没问题的。
就是注意一点,如果用于盈利的话,就涉及侵权,侵权违法,不要说是我教的。

项目四:网易云歌曲爬虫

import random,re,os,time
import requests

def downloader(url,name):
#----下载器----#
    size=0 #定义已下载文件的大小(byte)
    chunk_size=1024 #定义每次下载的数据大小(byte)
    root='./蔡徐坤动听音乐/'
    path=root+ name + '.mp3'
    try:
        # 如果文件夹不存在,则创建文件夹
        if not os.path.exists(root):
            os.mkdir(root)
        if not os.path.exists(path):
            res=requests.get(url,stream=True,headers=headers)
            content_size=int(res.headers['content-length'])
            if res.status_code==200:
                print('[文件大小]:%0.2f MB'%(content_size/1024/1024)) #将byte换算成MB
                print("利神超极速爬虫器正在疯狂下载!!!".center(50,'-'))            
                with open(path,'wb') as f:
                    for data in res.iter_content(chunk_size=chunk_size):
                        f.write(data)
                        size+=len(data)
                        print("\r[下载进度]:{}{:.2f}%".format('>'*int(size*50/content_size),size*100/content_size),end='')
                    print(f'\n下载成功,文件自动保存在当前目录{path}')
        else:
            print('文件已存在')
    except:
        print('爬取失败')

# 伪装请求头
user_agent = [
    'Mozilla/5.0 (Windows NT 5.2) AppleWebKit/534.30 (KHTML, like Gecko) Chrome/12.0.742.122 Safari/534.30',
    'Mozilla/5.0 (Windows NT 5.1; rv:5.0) Gecko/20100101 Firefox/5.0',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)',
    'Opera/9.80 (Windows NT 5.1; U; zh-cn) Presto/2.9.168 Version/11.50',
    'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/533.21.1 (KHTML, like Gecko) Version/5.0.5 Safari/533.21.1',
    'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET4.0E; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET4.0C)',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
]
headers = {'User-Agent': random.choice(user_agent)}

# 爬取歌手页
url = 'https://music.163.com/artist?id=12932368'
res=requests.get(url,headers=headers)

#用正则表达式提取id和歌曲名,<a href="/song?id=1360512113">记得</a>
pattern=re.compile('<a href="/song\?id=(\d+)">.*?</a>')
ids=re.findall(pattern,res.text)
pattern=re.compile('<a href="/song\?id=\d+">(.*?)</a>')
names=re.findall(pattern,res.text)

# 批量下载歌曲
for id,name in zip(ids,names):
    url = f'https://music.163.com/song/media/outer/url?id={id}'
    downloader(url,name)
    # res=requests.get(url,headers=headers)
    # file = open(f'{name}.mp3','wb')
    # file.write(res.content)
    # file.close()

input('爬虫完毕,不要问谁开发的,深藏功与名,按任意键退出')

项目五:有道词典接口爬虫

一、制作背景

新手玩家喜欢爬资源,中级玩家喜欢爬数据,高级玩家喜欢爬接口。

最近做了一个翻译接口,这下看不懂的英语可以直接翻译,无需外网,翻译几遍总能记住,这下总没借口了吧。
原理其实很简单,当然不是我的电脑放了一个本地词典,因为大家其实也发现了,这个是可以翻译句子的,而且支持几十种语言。🤠
实际上是通过破解有道词典的翻译接口,最终的翻译服务并不是我提供的,如果某一天接口不能用的,应该就是有道词典的加密系统维护升级了😅

别人的服务花了大力气做的接口,爬虫弄来放在自己的站点,如果用来盈利的话就违法了,不过我就是放在内网供班级使用,也不盈利,有道词典拿我也没辙。

二、接口调用

HTTP 方法:POST

请求URL:http://10.163.72.55:5000/todo1

URL参数:

​ data:需要翻译的内容

返回:

​ data{

​ content:翻译完成的内容

​ }

三、请求示例

1.Python

from requests import put, get

html=put('http://10.163.72.55:5000/todo1', data={'data': 'I am a student'}).json()
print (html)

2.PHP

<?php
$content = $_POST['content'];  

// 解码,Unicode转义(\uXXXX)的编码和解码
function decodeUnicode($str)
{
    return preg_replace_callback('/\\\\u([0-9a-f]{4})/i',
        create_function(
            '$matches',
            'return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");'
        ),
        $str);
}

function curlrequest($url,$data,$method='post'){
    $ch = curl_init(); //初始化CURL句柄
    curl_setopt($ch, CURLOPT_URL, $url); //设置请求的URL
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); //设为TRUE把curl_exec()结果转化为字串,而不是直接输出
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); //设置请求方式
    curl_setopt($ch,CURLOPT_HTTPHEADER, array( "X-HTTP-Method-Override: $method")); //设置HTTP头信息
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data); //设置提交的字符串
    $document = curl_exec($ch); //执行预定义的CURL
    curl_close($ch);
    return $document;
    }
    $url = 'http://10.163.72.55:5000/todo1';
    $data = "data=$content";
    $return = curlrequest($url, $data, 'put');

    // ajax异步返回数据
    $a = array();
    $a['content'] = $return;
    echo json_encode($a);
    exit;
?>

四、制作前端窗口

教大家如何制作一个漂亮的前端窗口,既然是做盗版软件就做得像一点。

import requests
import json
from tkinter import Tk,Button,Entry,Label,Text,END

# 对利神翻译接口发起请求,获得数据
class LiShenFanYi(object):
    def __init__(self):
        pass
    def crawl(self,word):
        html = requests.put('http://10.163.72.55:5000/todo1', data={'data': word}).json()
        return html

# 前段窗口应用程序
class Application(object):
    def __init__(self):
        self.window = Tk()
        self.fanyi = LiShenFanYi()
        self.window.title(u'我的翻译')  # 设置标题

        #设置窗口大小和位置
        self.window.geometry('310x370+500+300')
        self.window.minsize(310,370)
        self.window.maxsize(310,370)

        #创建一个文本框
        #self.entry = Entry(self.window)
        #self.entry.place(x=10,y=10,width=200,height=25)
        #self.entry.bind("<Key-Return>",self.submit1)
        self.result_text1 = Text(self.window,background = 'azure') # 设置背景颜色
        self.result_text1.place(x = 10,y = 5,width = 285,height = 155)
        self.result_text1.bind("<Key-Return>",self.submit1)

        #创建一个按钮,并为按钮添加事件
        self.submit_btn = Button(self.window,text=u'翻译',command=self.submit)
        self.submit_btn.place(x=205,y=165,width=35,height=25)
        self.submit_btn2 = Button(self.window,text=u'清空',command = self.clean)
        self.submit_btn2.place(x=250,y=165,width=35,height=25)

        #翻译结果标题
        self.title_label = Label(self.window,text=u'翻译结果:')
        self.title_label.place(x=10,y=165)

        #翻译结果
        self.result_text = Text(self.window,background = 'light cyan')
        self.result_text.place(x = 10,y = 190,width = 285,height = 165)

    # 回车翻译
    def submit1(self,event):
        #从输入框获取用户输入的值
        content = self.result_text1.get(0.0,END).strip().replace("\n"," ")
        #把这个值传送给服务器进行翻译
        result = self.fanyi.crawl(content)
        #将结果显示在窗口中的文本框中
        self.result_text.delete(0.0,END)
        self.result_text.insert(END,result)
        #print(content)

    def submit(self):
        #从输入框获取用户输入的值
        content = self.result_text1.get(0.0,END).strip().replace("\n"," ")
        #把这个值传送给服务器进行翻译
        result = self.fanyi.crawl(content)
        #将结果显示在窗口中的文本框中
        self.result_text.delete(0.0,END)
        self.result_text.insert(END,result)
        print(content)
    
    #清空文本域中的内容
    def clean(self):
        self.result_text1.delete(0.0,END)
        self.result_text.delete(0.0,END)

    def run(self):
        self.window.mainloop()

if __name__=="__main__":
    app = Application()
    app.run()

文章作者: 彭韦浩
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 彭韦浩 !
  目录