盘点一道Pandas中分组聚合groupby()函数用法的基础题

大家好,我是皮皮。

一、前言

前几天在Python最强王者交流群有个叫【Chloé】的粉丝问了一个关于Pandas中groupby函数的问题,这里拿出来给大家分享下,一起学习。

这个粉丝在法国留学,所以提问是英文的,当然了英文看上去也不难,有点二级英文基础,也看得懂,实在不行,在线翻译一下也问题不大了。

二、解决过程

这里给出【dcpeng】大佬和【月神】的解答。

【dcpeng】的解答

gruopby是分组的意思,这个我们都知道。python中groupby函数主要的作用是进行数据的分组以及分组后的组内运算!

对于数据的分组和分组运算主要是指groupby函数的应用,具体函数的规则如下:

df.groupby([df[属性],df[属性])(指分类的属性,数据的限定定语,可以有多个).mean()(对于数据的计算方式——函数名称)

如上图所示,前面是没问题的,后面有个地方他的解析有点问题,好在有【月神】补充,一起来看看吧!

【月神】的解答

从这个图里可以看出来使用driver_gender列对data进行聚合后再对search_conducted列进行分组求和。.sum()就是求和函数,对指定数据列进行相加。

上面【dcpeng】大佬认为这个search_conducted是个函数,其实不是,它是指分类的属性,数据的限定,search_conducted是具体的列,需要结合数据查看。其实说白了,就只是针对分组后的search_conducted列进行sum求和。

【月神】在这里还多了一些拓展,详情如下图所示。

此图一出,小伙伴们直呼好家伙,确实太清晰了,一目了然!

三、总结

大家好,我是Python进阶者。这篇文章基于粉丝提问,针对Pandas中分组聚合groupby()函数用法的基础题问题,给出了具体说明和演示,顺利地帮助粉丝解决了问题。

总的来说,python中groupby函数主要的作用是进行数据的分组以及分组后的组内运算!对于数据的分组和分组运算主要是指groupby函数的应用,具体函数的规则如下:

df.groupby([df[属性],df[属性])(指分类的属性,数据的限定定语,可以有多个).mean()(对于数据的计算方式——函数名称)

最后感谢粉丝【Chloé】提问,感谢【月神】和【dcpeng】大佬给出的示例和代码支持,感谢粉丝【冫马讠成】、【Zhang Zhiyu】、【PI】、【东哥】、【艾希·觉罗】、【༺ཉི།།与歌同行ཉྀ༻】、【Toria D.】、【༺⃰ཊ⃰-听风-ཏ⃰༻⃰】等人参与学习交流。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

分享一场Python正则表达式中的特殊序列问答交流

大家好,我是我是皮皮。

一、前言

前几天在Python最强王者交流群有个叫【杰】的粉丝问了一个关于Python正则表达式的问题,讨论十分火热,这里拿出来给大家分享下,一起学习。

下图是原始题目:

下面是给出的答案,乍看有点难懂,不过有正则狂魔【小王】大佬在,问题不大。

二、解决过程

这个这里给出【小王】大佬的解答,一起来看看吧,下面是他给的一个示例代码。

import re

# 命名分组对应命名引用
pattern = re.compile(r"(?P<num>\d+).*?(?P=num)")
txt = "123你好呀123"
print(re.findall(pattern, txt))

# 匿名分组对应匿名引用
pattern = re.compile(r"(\d+).*?\1")
txt = "123你好呀123"
print(re.findall(pattern, txt))

# 命名分组对应匿名引用
pattern = re.compile(r"(\d+).*?\1")
txt = "123你好呀123"
print(re.findall(pattern, txt))

输出结果如下图所示:

关于输出的解析如下:

不过这么说还是不太好理解,这里【小王】大佬又给出了另外一个简单的示例,代码如下:

txt = "测试文本123python   测试文本python~"
pattern = re.compile(r"([一-龟]+)(\d+)(\w+)(\s+)(\1)(\3).*")
print(re.findall(pattern, txt))

输出结果如下图所示:

这下就清晰多了!

三、总结

大家好,我是皮皮。这篇文章基于粉丝提问,针对Python正则表达式中的特殊序列问题,给出了具体说明和演示,顺利的帮助粉丝解决了问题。

最后感谢粉丝【杰】提问,感谢【小王】、【(这是月亮的背面)】、【dcpeng】、【wangning】、【Chloé P.】等大佬们参与学习交流。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

Python实现:与其穷举构造字典,不如直接用《百家姓》返回其index

大家好,我是我是皮皮。

一、前言

前几天在Python最强王者交流群【小王】大佬分享了一个Python代码,关于百家姓的,按百家姓排名名字顺序。

这个方法虽好,但是【德善堂小儿推拿-瑜亮老师】大佬觉得与其穷举构造字典,不如直接用《百家姓》返回其index,在粉丝【mofyl】的请求下,完成了代码编写。

二、解决过程

下面这个是针对单字姓的,百家姓是从网上直接复制的,返回其index实现的代码如下所示:

import re
a = '''
赵钱孙李,周吴郑王。冯陈褚卫,蒋沈韩杨。朱秦尤许,何吕施张。孔曹严华,金魏陶姜。
戚谢邹喻,柏水窦章。云苏潘葛,奚范彭郎。鲁韦昌马,苗凤花方。俞任袁柳,酆鲍史唐。
费廉岑薛,雷贺倪汤。滕殷罗毕,郝邬安常。乐于时傅,皮卞齐康。伍余元卜,顾孟平黄。
和穆萧尹,姚邵湛汪。祁毛禹狄,米贝明臧。计伏成戴,谈宋茅庞。熊纪舒屈,项祝董梁。
杜阮蓝闵,席季麻强。贾路娄危,江童颜郭。梅盛林刁,钟徐邱骆。高夏蔡田,樊胡凌霍。
虞万支柯,昝管卢莫。经房裘缪,干解应宗。丁宣贲邓,郁单杭洪。包诸左石,崔吉钮龚。
程嵇邢滑,裴陆荣翁。荀羊於惠,甄曲家封。芮羿储靳,汲邴糜松。井段富巫,乌焦巴弓。
牧隗山谷,车侯宓蓬。全郗班仰,秋仲伊宫。宁仇栾暴,甘钭厉戎。祖武符刘,景詹束龙。
叶幸司韶,郜黎蓟薄。印宿白怀,蒲邰从鄂。索咸籍赖,卓蔺屠蒙。池乔阴鬱,胥能苍双。
闻莘党翟,谭贡劳逄。姬申扶堵,冉宰郦雍。郤璩桑桂,濮牛寿通。边扈燕冀,郏浦尚农。
温别庄晏,柴瞿阎充。慕连茹习,宦艾鱼容。向古易慎,戈廖庾终。暨居衡步,都耿满弘。
匡国文寇,广禄阙东。欧殳沃利,蔚越夔隆。师巩厍聂,晁勾敖融。冷訾辛阚,那简饶空。
曾毋沙乜,养鞠须丰。巢关蒯相,查后荆红。游竺权逯,盖益桓公。
'''
b = re.sub(r'[,。\n]', '', a)
c = {k: v for k, v in enumerate(b)}
print(c)

输出结果如下图所示:

单姓的容易实现,那么复姓的会不会出不来呢?

复姓实现的代码如下所示:

fx = '''
万俟,司马,上官,欧阳,夏侯,诸葛,闻人,东方,
赫连,皇甫,尉迟,公羊,澹台,公冶,宗政,濮阳,
淳于,单于,太叔,申屠,公孙,仲孙,轩辕,令狐,
钟离,宇文,长孙,慕容,鲜于,闾丘,司徒,司空,
丌官,司寇,仉督,子车,颛孙,端木,巫马,公西,
漆雕,乐正,壤驷,公良,拓跋,夹谷,宰父,谷梁,
晋楚,闫法,汝鄢,涂钦,段干,百里,东郭,南门,
呼延,归海,羊舌,微生,岳帅,缑亢,况郈,有琴,
梁丘,左丘,东门,西门,商牟,佘佴,伯赏,南宫,
墨哈,谯笪,年爱,阳佟,第五,言福'''

fx_list = fx.replace("\n", "").split(",")
fx_dict = {k: v for k, v in enumerate(fx_list)}
print(fx_dict)

输出结果如下图所示:

小伙伴们直呼好家伙,完美解决,

后面【(这是月亮的背面)】大佬提出不需要推导式,也可以实现,直接上代码,如下所示:

dict(enumerate(fx.replace('\n', '').split(',')))

实现的效果是一样的,如下图所示:

姓氏应该放在key上,索引值放到value上,方便索引查找的问题,可以根据实际需要和程序员的代码习惯去放键值的位置,至于具体的代码编写,就要根据需求了。

三、总结

大家好,我是皮皮。这篇文章基于粉丝提问,针对百家姓中姓氏排名问题,给出了具体说明和演示,顺利地帮助粉丝解决了问题。不过这里也只是针对该具体场景给出的答案,如果换个场景,可能就不太适用了。

最后感谢粉丝【mofyl】提问,感谢【小王】、【德善堂小儿推拿-瑜亮老师】、【(这是月亮的背面)】、【dcpeng】、【冫马讠成】、【mofyl】、【东哥】大佬给予的支持和积极参与学习。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

Python匿名函数lambda x: x-0 and x-1代表的意思是什么…

大家好,我是我是皮皮。

一、前言

前几天在Python最强王者交流群有个叫【Chloé P.】的粉丝问了一个关于Python匿名函数的问题,讨论十分火热,这里拿出来给大家分享下,一起学习。

题目如下图所示:

下面是给出的答案,乍看有点难懂。

二、解决过程

这个这里给出【杰】大佬的答案,一起来看看吧。

下面是fliter这个函数的含义:

不过看上去还是有点晦涩难懂,后来【德善堂小儿推拿-瑜亮老师】大佬给了一个更加直白的解答,如下图所示:

保留后面5个元素,即原来元组中的2,3,4,5,6。

你以为这样就完事了?其实没有,后来【(这是月亮的背面)】大佬补充说明了,详情如下图所示:

三、总结

大家好,我是皮皮。这篇文章基于粉丝提问,针对匿名函数的理解问题,给出了具体说明和演示,顺利地帮助粉丝解决了问题。

最后感谢粉丝【Chloé P.】提问,感谢【杰】、【德善堂小儿推拿-瑜亮老师】、【(这是月亮的背面)】大佬们给予的帮助支持。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

Jsrpc学习——加密参数Sign变化的网站破解教程

大家好,我是皮皮。前几天给大家分享jsrpc的介绍篇,Python网络爬虫之js逆向之远程调用(rpc)免去抠代码补环境简介,还有实战篇,Jsrpc学习——网易云热评加密函数逆向,Jsrpc学习——Cookie变化的网站破解教程感兴趣的小伙伴可以戳此文前往。

今天给大家来个jsrpc实战教程,让大家加深对jsrpc的理解和认识。下面是具体操作过程,不懂的小伙伴可以私我。

今天我们介绍的这个网站是cookie参数不变,但是加密参数Sign变化的一个网站,一起来使用jsrpc来攻破它吧!

1、这里使用的网站是87aed0b6bc8cb687d63dd7eee0f64d38,MD5加密处理过的。

2、需要提取100个网页中的数字,然后求和。

3、打开浏览器抓包,然后打断点调试,依次点击右边的Call Stack内的东西,直到找到加密函数,里边的值对应请求参数即可判定。

4、最终在这里找到了一堆人看不懂的东西。

5、仔细寻找,发现加密的函数在这里了。

6、之后可以在控制台输入指令window.dcpeng = window.get_sign,其中window.get_sign为加密函数。注意:这个地方挺重要的,很多时候我们会写成ct.update(),这样会有问题!加了括号就是赋值结果,没加就是赋值整个函数!千差万别。

7、关闭网页debug模式。注意:这个地方挺重要的,很多时候如果不关闭,ws无法注入!

8、此时在本地双击编译好的文件win64-localhost.exe,启动服务。

9、之后在控制台注入ws,即将JsEnv.js文件中的内容全部复制粘贴到控制台即可(注意有时要放开断点)。

10、连接通信,在控制台输入命令var demo = new Hlclient(“ws://127.0.0.1:12080/ws?group=para&name=test”);

11、随后继续输入命令:

// 注册一个方法 第一个参数get_v为方法名,
// 第二个参数为函数,resolve里面的值是想要的值(发送到服务器的)
// param是可传参参数,可以忽略
demo.regAction("get_para", function (resolve) {
    dcpeng();
 var res = window.sign
    resolve(res);
})

也许有小伙伴会觉得奇怪,window.sign明明是在list这个变量中,为啥我们通过window.get_sign()可以获取到,莫非window.get_sign()和window.sign返回的值是一样的?其实window是整个全局,它只是声名一个list对象里面有signature等于全局的sign,这个全局的sign的值通过window.get_sing()得到。

dcpeng()就是一个函数,里面写的最后结果就是window.sign=window.get_sign(),并没有return东西。

12、之后就可以在浏览器中访问数据了,打开网址 http://127.0.0.1:12080/go?group={}&name={}&action={}¶m={} ,这是调用的接口 group和name填写上面注入时候的,action是注册的方法名,param是可选的参数,这里续用上面的例子,网页就是:http://127.0.0.1:12080/go?group=para&name=test&action=get_para

13、如上图所示,我们看到了那个变化的参数v的值,直接通过requests库可以发起get请求。

14、现在我们就可以模拟数据,进行请求发送了。

15、将拷贝的内容可以丢到这里进行粘贴:http://tool.yuanrenxue.com/curl

16、之后将右侧的代码复制到Pycharm中即可用,非常便利。

17、之后就可以构造请求了,加一个整体循环,然后即可获取翻页的内容,整体代码如下所示。

import requests
import json

cookies = {
    'session': '6c78df1c-37aa-4574-bb50-99784ffb3697.Qcl0XN6livMeZ-7tbiNe-Ogn8L4',
    'v': 'A7s8gqX6XgjWtmKFwCNKPNdQSpQgEM9-ySWTzq14lzDRLtVKNeBfYtn0IxW-',
}

headers = {
    'Connection': 'keep-alive',
    'Accept': 'application/json, text/javascript, */*; q=0.01',
    'X-Requested-With': 'XMLHttpRequest',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.99 Safari/537.36 Edg/97.0.1072.69',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'Origin': 'http://spider.wangluozhe.com',
    'Referer': 'http://spider.wangluozhe.com/challenge/2',
    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
}
all_data = []
for page_num in range(1, 101):
    sign_url = 'http://127.0.0.1:12080/go?group=para&name=test&action=get_para'
    sign = requests.get(url=sign_url).json()["get_para"]
    # print(sign)
    data = {
      'page': f'{page_num}',
      'count': '10',
      '_signature': sign
    }
    print(f'Crawlering page {page_num}')

    response = requests.post('87aed0b6bc8cb687d63dd7eee0f64d38', headers=headers, cookies=cookies, data=data, verify=False).json()
    for item in response["data"]:
        all_data.append(item["value"])
        # print(item["value"])

print(sum(all_data))

运行结果如上图所示,和网页上呈现的数据一模一样。

18、至此,请求就已经完美的完成了,如果想获取全部网页,构造一个range循环翻页即可实现。

17、也欢迎大家挑战该题目,我已经挑战成功了,等你来战!

总结

大家好,我是皮皮。这篇文章主要给大家介绍了jsrpc的实战教程,使用jsrpc工具可以在网络爬虫过程中事半功倍,无需仔细的去扣环境,去一步步逆向,只一个黑盒的模式,我们就拿到了想要的结果,屡试不爽。

初次接触jsrpc的小伙伴可能看不懂,这里还有黑哥录制的一个视频,大家可以对照着视频进行学习,地址:https://www.bilibili.com/video/BV1EQ4y1z7GS,黑哥全程无声演示,视频的BGM很大,建议大家可以静音播放,领会其中奥义。

关于jsrpc工具,可以点击原文前往获取。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

手把手教你开展mofish库(摸鱼库)的打包发布

大家好,我是我是皮皮。

一、前言

前几天吴老板给我推荐了一个摸鱼库,竟然是Python库,给我惊了一下,感觉应缺斯汀。

手把手教你开展mofish库(摸鱼库)的打包发布

要知道之前他就已经写了一篇关于摸鱼的文章,感兴趣的小伙伴,可以前往:手把手教你使用Python打造一款摸鱼倒计界面。

如今他把这个摸鱼做成了一个Python库,关于这个库的使用之前已经发布了文章,可以前往:盘点一个名为摸鱼的Python库,一起来摸鱼吧!

在评论区看到了【有点意思】大佬的留言,如下图所示:

手把手教你开展mofish库(摸鱼库)的打包发布

大概意思是把自己写的代码,封装成Python库,人人都可用那种,这里给安排上,这篇文章就是讲如何打包发布的,一起来看看吧!

二、代码

首先准备好代码,这个代码,之前的文章就已经分享了,这里不再赘述,代码在这里了。

# -*- coding: utf-8 -*-
import datetime

import click
from zhdate import ZhDate as lunar_date


def get_week_day(date):
    week_day_dict = {
        0: '星期一',
        1: '星期二',
        2: '星期三',
        3: '星期四',
        4: '星期五',
        5: '星期六',
        6: '星期天',
    }
    day = date.weekday()
    return week_day_dict[day]


def time_parse(today):
    distance_big_year = (lunar_date(today.year, 1, 1).to_datetime().date() - today).days
    distance_big_year = distance_big_year if distance_big_year > 0 else (
            lunar_date(today.year + 1, 1, 1).to_datetime().date() - today).days

    distance_5_5 = (lunar_date(today.year, 5, 5).to_datetime().date() - today).days
    distance_5_5 = distance_5_5 if distance_5_5 > 0 else (
            lunar_date(today.year + 1, 5, 5).to_datetime().date() - today).days

    distance_8_15 = (lunar_date(today.year, 8, 15).to_datetime().date() - today).days
    distance_8_15 = distance_8_15 if distance_8_15 > 0 else (
            lunar_date(today.year + 1, 8, 15).to_datetime().date() - today).days

    distance_year = (datetime.datetime.strptime(f"{today.year}-01-01", "%Y-%m-%d").date() - today).days
    distance_year = distance_year if distance_year > 0 else (
            datetime.datetime.strptime(f"{today.year + 1}-01-01", "%Y-%m-%d").date() - today).days

    distance_4_5 = (datetime.datetime.strptime(f"{today.year}-04-05", "%Y-%m-%d").date() - today).days
    distance_4_5 = distance_4_5 if distance_4_5 > 0 else (
            datetime.datetime.strptime(f"{today.year + 1}-04-05", "%Y-%m-%d").date() - today).days

    distance_5_1 = (datetime.datetime.strptime(f"{today.year}-05-01", "%Y-%m-%d").date() - today).days
    distance_5_1 = distance_5_1 if distance_5_1 > 0 else (
            datetime.datetime.strptime(f"{today.year + 1}-05-01", "%Y-%m-%d").date() - today).days

    distance_10_1 = (datetime.datetime.strptime(f"{today.year}-10-01", "%Y-%m-%d").date() - today).days
    distance_10_1 = distance_10_1 if distance_10_1 > 0 else (
            datetime.datetime.strptime(f"{today.year + 1}-10-01", "%Y-%m-%d").date() - today).days

    time_ = [
        {"v_": 5 - 1 - today.weekday(), "title": "周末"},  # 距离周末
        {"v_": distance_year, "title": "元旦"},  # 距离元旦
        {"v_": distance_big_year, "title": "过年"},  # 距离过年
        {"v_": distance_4_5, "title": "清明节"},  # 距离清明
        {"v_": distance_5_1, "title": "劳动节"},  # 距离劳动
        {"v_": distance_5_5, "title": "端午节"},  # 距离端午
        {"v_": distance_8_15, "title": "中秋节"},  # 距离中秋
        {"v_": distance_10_1, "title": "国庆节"},  # 距离国庆
    ]

    time_ = sorted(time_, key=lambda x: x['v_'], reverse=False)
    return time_


@click.command()
def cli():
    """你好,摸鱼人,工作再累,一定不要忘记摸鱼哦 !"""
    from colorama import init, Fore
    init(autoreset=True)  # 初始化,并且设置颜色设置自动恢复
    print()
    today = datetime.date.today()
    now_ = f"{today.year}{today.month}{today.day}日"
    week_day_ = get_week_day(today)
    print(f'\t\t {Fore.GREEN}{now_} {week_day_}')
    str_ = '''
    你好,摸鱼人,工作再累,一定不要忘记摸鱼哦 ! 
    有事没事起身去茶水间去廊道去天台走走,别老在工位上坐着。
    多喝点水,钱是老板的,但命是自己的 !
    '''
    print(f'{Fore.RED}{str_}')

    time_ = time_parse(today)
    for t_ in time_:
        print(f'\t\t {Fore.RED}距离{t_.get("title")}还有: {t_.get("v_")}天')
    tips_ = '''
    [友情提示] 三甲医院 ICU 躺一天平均费用大概一万块。
    你晚一天进 ICU,就等于为你的家庭多赚一万块。少上班,多摸鱼。\n
    '''
    print(f'{Fore.RED}{tips_}')
    print(f'\t\t\t\t\t\t\t{Fore.YELLOW} 摸鱼办')


if __name__ == '__main__':
    cli()

click 库的使用

注意到我们上面的文件代码用到了 click 库。

Python 内置了一个 Argparse 的标准库用于创建命令行, 但使用起来有些繁琐,Click 相比于 Argparse 可谓是小巫见大巫了。

安装

pip install click

click 的简单使用

官方文档的入门例子:

import click
  
@click.command()
@click.option('--count', default=1, help='Number of greetings.')
@click.option('--name', prompt='Your name', help='The person to greet.')
def hello(count, name):
    """Simple program that greets NAME for a total of COUNT times."""
    for x in range(count):
        click.echo('Hello %s!' % name)
  
if __name__ == '__main__':
    hello()
  • @click.command() 使函数 hello 成为命令行接口。
  • @click.option 的第一个参数指定了命令行选项的名称。
  • click.echo 方法和 python 内置的 print 方法相似。

使用方法:

打印 10 个 皮老板。

$ python hello.py --count 10 --name 皮老板    # 指定 count 和 name 的值
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!
Hello 皮老板!

setuptool 打包发布

安装依赖

pip install setuptools
pip install twine

打包上传

python setup.py sdist
twine upload dist/*

登录 pypi 账户并发布 python 库

setup.py 示例

from setuptools import setup, find_packages

description = '你好,摸鱼人,工作再累,一定不要忘记摸鱼哦! 有事没事起身去茶水间去廊道去天台走走,别老在工位上坐着。多喝点水,钱是老板的,但命是自己的!'

setup(
    name='mofish', # 库名
    version='1.0.0', # 版本号
    description=description, # 短简介
    long_description_content_type='text/markdown', 
    classifiers=[
        'Development Status :: 4 - Beta',
        'Environment :: Console',
        'Intended Audience :: Developers',
        'Intended Audience :: Information Technology',
        'Intended Audience :: System Administrators',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3 :: Only',
        'Programming Language :: Python :: 3.7',
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3.9',
        'Topic :: Internet',
        'Topic :: Software Development :: Libraries :: Python Modules',
        'Topic :: System :: Clustering',
        'Topic :: System :: Distributed Computing',
        'Topic :: System :: Monitoring',
        'Topic :: System :: Systems Administration',
    ],
    python_requires='>=3.7', # py 版本限制
    author='PY-GZKY', # 作者
    author_email='341796767@qq.com', # 邮件
    url='https://github.com/PY-GZKY/Mofish', # git
    license='MIT', # 开源协议
    packages=find_packages(), # 包
    include_package_data=True,
    entry_points="""
        [console_scripts]
        moyu=src.main:cli
    """, # 启动命令行脚本的文件
    install_requires=[
        'click>=6.7',
        'zhdate'
    ], # 限制安装库的版本

)

安装并使用

pip install mofish
moyu

代码仓库地址:

https://github.com/PY-GZKY/Mofish
手把手教你开展mofish库(摸鱼库)的打包发布

总结

大家好,我是皮皮。这篇文章主要是给大家盘点了一个Python库,基于这个库,本文介绍了如何把自己写的代码封装成Python库,打包上传,并且发布到pypi,到后面人人都可以用你的库。

最后感谢【吴老板】大佬给予的思路和代码支持,感谢【有点意思】大佬提的需求。

[友情提示] 三甲医院 ICU 躺一天平均费用大概一万块。你晚一天进 ICU,就等于为你的家庭多赚一万块。少上班,多摸鱼。

手把手教你开展mofish库(摸鱼库)的打包发布

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

手把手教你使用JavaScript打造一款扫雷游戏

大家好,我是皮皮。

扫雷大家都玩过,今天我们就是用JavaScript来打造扫雷游戏。废话不多说,直接看下效果;

上图是失败后的结果。

一、思路分析

我们新建一个首页,在首页放置一个点击开始游戏的按钮,动态生成100个小格,即100div;然后通过点击div进行扫雷操作,然后扫雷成功或者失败显示对应的结果;

二、静态页面搭建

2.1 结构层

<body>
    <div class="wrapper">
        <div class="btn" id="btn"></div>  <!-- 开始游戏按钮-->
        <div class="box" id="box"></div>  <!-- 存放小雷的div-->
        <div class="flagBox" id="flagBox"> <!-- 游戏结束才显示的当前雷数的div-->
            当前剩余雷数:
            <span id="score">10</span>
        </div>
        <div class="alertBox" id="alertBox">  <!-- Game over弹出的框(窗口)-->
            <div class="alertImg" id="alertImg">
                <div class="close" id="close"></div>
            </div>
        </div>
    </div>
</body>

2.2 样式层

清楚默认边距

*{
    margin:0;
    padding:0;
}

页面最大div

.wrapper {
    width:100%;
    height:1000px;
    position: fixed;
    top:0;
    left:0;
    background-image: url('img/bg.jpg');
    background-size: 100% 100%;
}

效果如下:

开始游戏按钮

.btn{
    height:140px;
    width:170px;
    position:absolute;
    left:50px;
    background-image: url('img/startGame.png');
    background-size: 100% 100%;
    cursor: pointer;
}

储存雷的大div

.box{
    height:500px;
    width:500px;
    transform: perspective(800px) rotateX(45deg);
    margin:20px auto;
    border-top:1px solid #B25F27;
    border-left:1px solid #B25F27;
    box-shadow:  5px 5px 5px rgba(0,0,0,0.3);
    display:none; /* 先设置为none,开始游戏后显示block */ 
}

每一个方块的小div(一共100个)

.block{
    width:49px;
    height:49px;
    border-right:1px solid #B25F27;
    border-bottom:1px solid #B25F27;
    box-shadow: 0 0 4px #333 inset;
    background-image: url('img/cao.jpg');
    float: left;
}

当前所剩雷数

.flagBox{
    position:absolute;
    top:50px;
    left:50%;
    width:200px;
    height:50px;
    margin-left:-100px;
    color:#333;
    font-size:20px;
    font-weight: bolder;
    display:none; /* 先设置为none,开始游戏后显示block */ 
}

Game Over

.alertBox{
    display:none; /* 先设置为none,开始结束显示block */ 
    position:absolute;
    width:100%;
    height:100%;
    left:0;
    top:0;
    background-color: rgba(0,0,0,0.2);
}

游戏结束弹出窗口右上角的X

.close{
    position:absolute;
    right:0;
    top:0;
    height:40px;
    width:40px;
    background-image: url('img/closeBtn.png');
    background-size: 100% 100%;
    cursor: pointer;

}

三、js页面交互

3.1 获取元素及变量初始化

var startBtn = document.getElementById('btn');
var box = document.getElementById('box');
var flagBox = document.getElementById('flagBox');
var alertBox = document.getElementById('alertBox');
var alertImg = document.getElementById('alertImg');
var closeBtn = document.getElementById('close');
var score = document.getElementById('score');
// 先声明变量,但是不初始化
var minesNum;
var mineOver;
var block;
var mineMap = [];
var startGameBool = true;

3.2 10个雷的初始化设置

function init() {
    minesNum = 10;
    mineOver = 10;
    score.innerHTML = mineOver;
    
    for (var i = 0; i < 10; i++) { // 双层循环 10 * 10 个div
        for (var j = 0; j < 10; j++) {
            var con = document.createElement('div');
            con.classList.add('block'); // 给创建出来的div添加类名 block 
            con.setAttribute('id', i + '-' + j);
            box.appendChild(con);
            mineMap.push({ mine: 0 });
        }
    }
    block = document.getElementsByClassName('block');
    while (minesNum) { // 创建一个10次的循环,即设置10个雷
        var mineIndex = Math.floor(Math.random() * 100);
        if (mineMap[mineIndex].mine === 0) {
            mineMap[mineIndex].mine = 1;
            block[mineIndex].classList.add('isLei'); // 10个雷有小div的block类属性,还有自己的属性,isLei
            minesNum--;
        }
    }
}

3.3 游戏开始事件封装

function bindEvent() {
    startBtn.onclick = function () { // 开始按钮点击事件
        if(startGameBool){
            box.style.display = 'block';
            flagBox.style.display = 'block';
            init();
            startGameBool = false;
        }
    }
    box.oncontextmenu = function () {
        return false;
    }
    box.onmousedown = function (e) { // 小div鼠标按下事件封装
        var event = e.target;
        if (e.which == 1) { //Netscape/Firefox/Opera中不支持 window.event.keyCode,需要用event.which代替
            leftClick(event);
        } else if (e.which == 3) {
            rightClick(event);
        }
    }
    closeBtn.onclick = function () { // 游戏结束,弹出game over窗口的关闭按钮事件封装
        alertBox.style.display = 'none';
        flagBox.style.display = 'none';
        box.style.display = 'none';
        box.innerHTML = '';
        startGameBool = true;
    }
}

3.4 核心事件函数封装

leftClick 没有雷 –> 显示数字(代表以当前小格为中心周围8个格的雷数)扩散(当前周围八个格没有雷) 有雷 –> game Over

function leftClick(dom) {
    if(dom.classList.contains('flag')){
        return;
    }
    var isLei = document.getElementsByClassName('isLei'); // 获得前面的10个雷的div
    if (dom && dom.classList.contains('isLei')) { // 判断是不是雷块
        for (var i = 0; i < isLei.length; i++) {
            isLei[i].classList.add('show'); // 显示地雷背景图
        }
        setTimeout(function () {
            alertBox.style.display = 'block';
            alertImg.style.backgroundImage = 'url("img/over.jpg")';  // 上面显示雷,标志游戏结束
        }, 800)
    } else { // 否则继续扫雷
        var n = 0;
        var posArr = dom && dom.getAttribute('id').split('-');
        var posX = posArr && +posArr[0];
        var posY = posArr && +posArr[1];
        dom && dom.classList.add('num');
        for (var i = posX - 1; i <= posX + 1; i++) {
            for (var j = posY - 1; j <= posY + 1; j++) {
                var aroundBox = document.getElementById(i + '-' + j);
                if (aroundBox && aroundBox.classList.contains('isLei')) {
                    n++;
                }
            }
        }
        dom && (dom.innerHTML = n);
        if (n == 0) {
            for (var i = posX - 1; i <= posX + 1; i++) {
                for (var j = posY - 1; j <= posY + 1; j++) {
                    var nearBox = document.getElementById(i + '-' + j);
                    if (nearBox && nearBox.length != 0) {
                        if (!nearBox.classList.contains('check')) {
                            nearBox.classList.add('check');
                            leftClick(nearBox);
                        }
                    }
                }
            }
        }
    }
}

rightClick 没有标记并且没有数字 –> 进行标记;

有标记 –> 取消标记 –> 标记是否正确,10个都正确标记,提示成功;

如果已经出现,则点击无效果;

function rightClick(dom){
    if(dom.classList.contains('num')){ // 如果已经出现,则点击无效果
        return;
    }
    dom.classList.toggle('flag'); // 在元素中切换类名,切换为flag类名,显示红旗背景图;此处的雷被扫除了
    if(dom.classList.contains('isLei') && dom.classList.contains('flag')){
        mineOver --; // 雷数减一
    }
    if(dom.classList.contains('isLei') && !dom.classList.contains('flag')){
        mineOver ++;
    }

    score.innerHTML = mineOver;
    if(mineOver == 0){ // 扫完雷,标志雷数量为0
        alertBox.style.display = 'block';
        alertImg.style.backgroundImage = 'url("img/success.png")'; // 游戏胜利
    }
}

3.5 游戏开始

bindEvent()

四、总结

本文我们通过JavaScript打造了简单的扫雷游戏,首先是设计下简单的界面样式,然后通过扫雷的逻辑动态构建雷块的位置,通过点击小方块进行扫雷,感兴趣的小伙伴可以去试一下。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

拼刀刀店铺后台的参数anti-content逆向分析

大家好,我是黑脸怪。今天给大家分享pin嘻嘻逆向。

分析网址:’aHR0cHM6Ly9tbXMucGluZHVvZHVvLmNvbS9nb29kcy9nb29kc19saXN0′

1.介绍-为什么要逆向anti-content参数

用代码访问后台数据不带anti参数的话会提示“访问频繁”,所以需要逆向出这个参数!

2.开始逆向js

2.1 找参数所在位置-先在控制台全局搜索参数名字

搜索出来10个结果 不算多,每个都点进去 在差不多的关键词位置打上断点先。

因为它Ajax请求每个页面都要anti参数 所以我们下断点之后随便点个按钮都能用 已经断下来了,这个aa1d开头的js文件(你们的不一定叫这名),可以把其他断点取消了,现在来着重分析这个。

2.2 跟栈-找加密的方法

这里是一个异步代码调用,我们把断点打到它上一层这里先。

return Promise.resolve(u(t.rawFetch, d).catch((function() {}

直接f8放过去 再重新点一次 会到达上面那个断点

f11单步调试进来 会跟到这里

getCrawlerInfo: function(t) {
                return Promise.resolve(G((function() {
                    var e = I.a.getInstance(t);
                    return Promise.resolve(e.getServerTime()).then(F)
                }
                ), (function() {
                    return ""
                }
                )))
            }

对于异步不太懂的 我这里强行演示一波 (百度小抄一下改改)

//1. Promise.resolve("111")

 Promise.resolve("111")
//Promise {<fulfilled>: '111'}
//    [[Prototype]]: Promise
//    [[PromiseState]]: "fulfilled"  这是完成的状态 
//    [[PromiseResult]]: "111"       这是结果


//2.Promise.resolve("").then(函数())


Promise.resolve("我是参数?").then(function(a){console.log("111",a);return "123"})
//111 我是参数?
//Promise {<fulfilled>: '123'}
//    [[Prototype]]: Promise
//    [[PromiseState]]: "fulfilled"
//    [[PromiseResult]]: "123"

所以e是时间戳,f是主要函数 下断点到这 f8直接过来 再单步两次到F

要的是里面的这串代码 :new一个对象 对象传入一个包含serverTime的对象这里我也不太理解 最后messagePack肯定就是方法了。。。不管他直接复制在控制台跑一下出结果了。

new (n("eDaA"))({
    serverTime: t
}).messagePack()

2.3 代码分析

n(“eDaA”) 我第一眼看这不就是个webpack吗 当时觉得还是以前的玩法 找到n方法的加载器 再复制eDaA这个模块就可以跑了。没想到跟进去发现 eDaA里面又是一个加载器和模块 第一次见到这样的 没玩过,研究了半天

eDaA导出fbeZ fbeZ又导出里面的整个webpack

所以最后我们只要fbeZ里面的webpack 跳过第一层直接取它, 因为它是第二层的 加载器不适用,需要找个通用的加载器

下面的加载器可以输出”111″就行

window=global;
!(function (e) {
    var i = {}
        , o = {
        index: 0
    }

    function c(t) {
        if (i[t])
            return i[t].exports;
        var n = i[t] = {
            i: t,
            l: !1,
            exports: {}
        };
        // console.log(t)
        return e[t].call(n.exports, n, n.exports, c),
            n.l = !0,
            n.exports
    }

    window.hliang1 = c
}
)([
    function(e,t,n){
        console.log("111")
    }
])
window.hliang1(0)

复制过来后 把前面列表的[和屁股后面的}]删除掉一个 因为会复制多

因为用notepad++代码格式化的问题,有一个模块会提示代码有问题

去网站重新粘贴一下这串代码到vscode(pycharm)

这样就完成了,用window.hliang1 就可以调用模块了

下面开始复制qe对象

它new 的qe对象就在模块里面啊,我不知道怎么直接new 所以新建了一个函数 然后对它改写

全部复制下来

function  hliang_qe(){

    //复制进这里来

}

(function (e, t) {}).call(this,a,b) 这种就是 把a,b传参到e,t

所以改写 匿名删除去掉,.call去掉,传参的e,t直接设置成

var e=window.hliang1(3) 还有其他地方n() 这里加载器名字改一下

t原本的作用是导出(t.exports)那我这里不要t了 直接导出改成return

如下图

复制到浏览器执行,成功出结果。但是这个代码在node.js还需要补环境和改环境。

3.环境检测

在浏览器能跑 在node.js跑不了 需要补环境。

这都啥报错啊,看不懂。 先上环境吧。


算了懒得写了。

直接告诉你们要补啥吧。

cookie和localStorage.Item传入自己的就行了 过期的也没事

window = global
document={
    addEventListener:function addEventListener(a,b){
        // console.log("addevent",a,b)
        return undefined
    },
    referrer:'',
    getElementById:function getElementById(a){
      console.log("getbyid",a)
      return "<head></head>"
    },
    cookie:''//这里传一个自己的cookie 过期了的也没事
}
var Plugins={0:{}}
navigator={
    webdriver:false,
    plugins: Plugins,
    languages:["zh-CN","zh"],
    hasOwnProperty:function hasOwnProperty(a){
      // console.log(a,"hasOwnProperty");
      if (a=="webdriver"){
        return false
      }

    },
    userAgent:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.81 Safari/537.36"
}

screen={availWidth:1920,availHeight:1040}
history={
  back:function back(){console.log("back",arguments)}
}
location={
  href:"https://mms.pinduoduo.com/goods/goods_list",
  port:""
}
chrome={}

localStorage={
  getItem:function getItem(a){
      
    // console.log("item",arguments)
    if (a=="_nano_fp"){
      return "" //这里也传一个自己的
    }
  }
}

window["chrome"]=chrome
window["location"]=location
window["navigator"]=navigator
window["history"]=history
window["document"]=document
window["screen"]=screen
window["localStorage"]=localStorage


Object.defineProperty && Object.defineProperty(window, "outerHeight", {
    value: 1040,
    writable: false
});
Object.defineProperty && Object.defineProperty(window, "outerWidth", {
    value: 1920,
    writable: false
});

function DeviceOrientationEvent(){
   console.log("DeviceOrientationEvent",arguments)
}
window["DeviceOrientationEvent"]=DeviceOrientationEvent
function DeviceMotionEvent(){
  console.log("DeviceMotionEvent",arguments)
 }
 window["DeviceMotionEvent"]=DeviceMotionEvent
//delete window.Buffer //e("0x3c", "anZ%")
document.getElementById.toString=function(){
    return 'function getElementById() { [native code] }'
}

可以了。环境+上面的代码就能跑了

{"success":true,"errorCode":1000000,"errorMsg":null,"result":{"sessionId":"e70ae011c9c64f8fbf0e70fada362385","total":0,"goods_list":[]}}

演示地址:

http://z.hl98.cn/index.php?share/file&user=102&sid=CiAXx7ry

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

盘点一个名为摸鱼的Python库,一起来摸鱼吧!

大家好,我是我是皮皮。

一、前言

前几天吴老板给我推荐了一个摸鱼库,竟然是Python库,给我惊了一下,感觉应缺斯汀。

盘点一个名为摸鱼的Python库,一起来摸鱼吧

要知道之前他就已经写了一篇关于摸鱼的文章,感兴趣的小伙伴,可以前往:

如今他把这个摸鱼做成了一个Python库,这里分享给大家,确实挺有趣的,每日没事出来摸摸鱼,也好!

二、使用方法

首先就是安装摸鱼库了,很简单,直接在命令行中输入安装指令:pip install mofish

盘点一个名为摸鱼的Python库,一起来摸鱼吧

安装完成之后,只需要输入命令moyu即可,如下图所示:

盘点一个名为摸鱼的Python库,一起来摸鱼吧

这样你就可以摸鱼了,没事的时候,在命令行输入moyu,看看温馨提示,看看距离放假还有多少天,心有所属,岂不美哉!

盘点一个名为摸鱼的Python库,一起来摸鱼吧

总结

大家好,我是皮皮。这篇文章主要是给大家盘点了一个Python库,名为mofish(摸鱼),使用方法简单,非常容易上手,摸鱼虽爽,但是别上瘾噢!

最后感谢【吴老板】大佬给予的思路和代码支持。

[友情提示] 三甲医院 ICU 躺一天平均费用大概一万块。你晚一天进 ICU,就等于为你的家庭多赚一万块。少上班,多摸鱼。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。

Jsrpc学习——网易云热评加密函数逆向

大家好,我是皮皮。

前几天给大家分享jsrpc的介绍篇,Python网络爬虫之js逆向之远程调用(rpc)免去抠代码补环境简介,感兴趣的小伙伴可以戳此文前往。今天给大家来个jsrpc实战教程,Jsrpc学习——Cookie变化的网站破解教程,让大家继续加深对jsrpc的理解和认识。下面是具体操作过程,不懂的小伙伴可以私我。

1、因为网易云音乐热评的加密并不在cookie上,而是参数加密,所以这里就不需要进行hook住cookie了。

2、之前就知道网易云音乐热评的加密存在之地是在下图的位置,是那个函数window.asrsea(JSON.stringify(i1x), bsR1x([“流泪”, “强”]), bsR1x(Xp8h.md), bsR1x([“爱心”, “女孩”, “惊恐”, “大笑”]))

3、找到了aes加密地方的函数,就赋值一个自己名字的全局变量,然后转发加密就行了。之后可以在控制台输入指令window.dcpeng = window.asrsea,其中window.asrsea为加密函数。注意:这个地方挺重要的,很多时候我们会写成ct.update(),这样会有问题!加了括号就是赋值结果,没加就是赋值整个函数!千差万别。

4、关闭网页debug模式。注意:这个地方挺重要的,很多时候如果不关闭,ws无法注入!

5、此时在本地双击编译好的文件win64-localhost.exe,启动服务。

6、之后在控制台注入ws,即将JsEnv.js文件中的内容全部复制粘贴到控制台即可(注意有时要放开断点)。

7、连接通信,在控制台输入命令var demo = new Hlclient(“ws://127.0.0.1:12080/ws?group=para&name=wangyiyun”);

8、随后继续输入命令:

// 注册一个方法 第一个参数get_v为方法名,
// 第二个参数为函数,resolve里面的值是想要的值(发送到服务器的)
// param是可传参参数,可以忽略
demo.regAction("get_para", function (resolve, param) {
 console.log(param);
 console.log(JSON.stringify(param));
    var res = dcpeng(param, '010001', '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7', '0CoJUm6Qyw8W8jud');
    resolve(JSON.stringify(res));
})

这里就用到了参数param,param也就是需要传值过来的json对象,因为你不可能获取固定的歌曲id和页码,所以用python写成字典,通过url编码写在param里。

9、这里为何不写成dcpeng(JSON.stringify(param)…)这样呢?因为get传过来的就是字符串,不用json转字符串了。

上图20行的位置那是json转字符串,因为只能返回字符串,返回对象的话会变成显示”[object]”

这两步只是控制台打印,不用管。

10、之后就可以在浏览器中访问数据了,打开网址 http://127.0.0.1:12080/go?group={}&name={}&action={}¶m={} ,这是调用的接口 group和name填写上面注入时候的,action是注册的方法名,param是可选的参数,这里续用上面的例子,网页就是:http://127.0.0.1:12080/go?group=para&name=wangyiyun&action=get_para¶m=rid=R_SO_4_1909604321&threadId=R_SO_4_1909604321&pageNo=1&pageSize=20&cursor=-1&offset=0&orderType=1

11、如上图所示,我们看到了那个变化的参数v的值,直接通过requests库可以发起get请求。

12、现在我们就可以模拟数据,进行请求发送了,整体代码如下所示。

import requests
import json
import urllib.parse

songid = '1908392914'
data = {
    "rid": f"R_SO_4_{songid}",
    "threadId": f"R_SO_4_{songid}",
    "pageNo": "1",
    "pageSize": "20",
    "cursor": "-1",
    "offset": "0",
    "orderType": "1",
    "csrf_token": ""
}
song_info = str(data)
param = (urllib.parse.quote(song_info))
param_url = f"http://127.0.0.1:12080/go?group=para&name=wangyiyun&action=get_para¶m={param}"
response = requests.get(url=param_url).text
response_json = json.loads(response)
get_para = json.loads(response_json["get_para"])
encText = get_para["encText"]
encSecKey = get_para["encSecKey"]
# print(encText)
# print(encSecKey)


data = {
    'params': encText,
    'encSecKey': encSecKey
}

response = requests.post('https://music.163.com/weapi/comment/resource/comments/get?csrf_token=', data=data)
print(response.text)

运行之后,可以得到网页上的评论数据:

放到json中去解析,和网页上呈现的数据一模一样。

下面是原网页上的原始数据:

13、至此,请求就已经完美的完成了,如果想获取全部网页,构造一个range循环翻页即可实现,改变请求参数中的pageNo即可。

14、如果想抓取不同的歌曲,只需要替换songid这个参数即可。

总结

大家好,我是皮皮。这篇文章主要给大家介绍了jsrpc的实战教程,使用jsrpc工具可以在网络爬虫过程中事半功倍,无需仔细的去扣环境,去一步步逆向,只一个黑盒的模式,我们就拿到了想要的结果,屡试不爽。

初次接触jsrpc的小伙伴可能看不懂,这里还有黑哥录制的一个视频,大家可以对照着视频进行学习,地址:https://www.bilibili.com/video/BV1EQ4y1z7GS,黑哥全程无声演示,视频的BGM很大,建议大家可以静音播放,领会其中奥义。

关于jsrpc工具,可以点击原文前往获取。

小伙伴们,快快用实践一下吧!如果在学习过程中,有遇到任何问题,欢迎加我好友,我拉你进Python学习交流群共同探讨学习。