出品 | 程序人生(ID:coder_life)
今年5月27日, 一位据说在德国的中国程序员@将记忆深埋在微博公布:
“半年时间,100多TB数据, 利用1024、91、sex8、PornHub、xvideos 等 站采集的数据对比Facebook、instagram、TikTok 、抖音、微博等 交媒体。我们在全球范围内成功识别了10多万从事不可描述行业的小姐姐。”
热炒之下,这套Deep Learning系统瞬间炸了锅,顺便炸翻了在德国处于懵逼状态的一众平时安安静静老老实实的程序员:我们身边竟然藏着这样一个人?!
这引起了我对在德中国籍程序员的行业分类以及专业方向等相关数据的好奇。长期以来德国一直面临着劳动力短缺,特别是工程技术方向,尤其是IT专业人才的极度缺乏,以至于德国政府将这些专业的人才获得欧盟蓝卡的最低年薪标准降到了税前41808欧元(2019)。换句话说,软件信息专业的同学毕业后在德国很容易找到工作,并且获得蓝卡工作居留许可。近年来身边来自印度,俄罗斯,中国的程序员也在逐年增加。那么中国程序员在德国到底从事那些行业呢?
蓝卡和德国程序员数据
先在 上找了一圈,没有找到特别针对中国籍程序员的数据分析,只找到关于蓝卡和在德国工作的程序员的数据分析。
蓝卡数据
2013-2018年,超过76000外籍人员持蓝卡在德国工作。2017年德国共有21727外国人申请蓝卡工作签证,其中中国国籍申请者占了近10%。这说明仅2017年,就有二千多中国籍雇员申请了蓝卡,这其中IT从业者占比未知。假设IT软件信息领域的中国雇员只占比其中10%,那么过去五年中就有约800名中国籍程序员拿到蓝卡。实际上根据生活和工作的接触,我保守估计在德中国籍程序员数量超过1500人。
据2016年数据,欧盟蓝卡签证的所在申请国,84%位于德国,可以说几乎整个欧盟的外国工程师都来德国找工作了。
在德国工作的程序员数据
据来自Stack Overflow的德国IT数据分析,2016年全德国有超过120000软件开发人员,2017年暴增超过820000。不过82万这个数字不可信,毕竟德国总人口才八千多万,如果是将近1%的占比,德国不至于一直闹码农荒。可信的十几万程序员中,软件开发方向数据如下:Web开发占比65.51%,系统管理员位居第二,数据库管理员第三。仅仅这三个方向就吃掉了75%的占比,为什么德国程序员看起来很偏科,爆火的机器学习和数据分析才各占4%左右。
这是因为德国IT行业大多为德国的支柱产业服务,如汽车、制药、机械、电子等,这些公司所需的企业内部管理软件如今多为SaaS构架,同时因为传统行业对云服务的怀疑和不信任态度,亦或安全原因,他们又维护着大量的企业私有服务器,和企业级数据库。所以不难理解前三甲总合占比之大。
虽说国内的移动开发趋势这两年有点弱,但德国的iOS和Android移动开发就从来没有强过,因为缺乏B2C土壤,传统企业一般也不重视移动开发(未必需要),相关产业很多都外包于东欧或者印度,中国的团队。
在德中国程序员数据分析
络上暂时没有发现任何关于这些可能存在的1500名中国程序员的数据,这就尴尬了,没数据怎么分析?
等等,平时管理的几个德国的IT行业微信群不就是最好的数据源?群友加起来也有500多人了,样本虽不大,但毕竟还是遵循正态分布的。不过必须用Python 3开发一套脚本来收集和处理相关数据。
在德中国程序员做什么
专业方向&工作领域&开发语言和框架的数据采集
如果使用匿名调查 告方式,扰民且又费时费力,此类信息只能从群昵称上打主意了,首先是发群公告规范群友昵称标准:
昵称|行业或专业领域|擅长开发框架或语言
举例:
小呆|学生|想找数据分析工作
中二|前端|nodejs, react
大傻|机器学习|nlp
老痴|自动驾驶|c++
大部分群友按标准改了昵称,但是还有一部分死硬派坚决不改,又不能经常发群消息提醒,只能开发机器人自动提醒了。微信机器人Wxpy是一个包装得非常简洁的微信个人 API, 在 itchat 的基础上,通过大量接口优化提升了模块的易用性,并进行丰富的功能扩展,一些常见的场景:
开发需求
该任务所需第三方库如下:
pip3 installwxpy
pip3 installapscheduler
pip3 installpymysql
pip3 installDBUtils
1. 建库建表
因为需要存储微信表情字符集,所以表的默认编码采用utf8mb4_unicode_ci。
DROPTABLEIFEXISTS`wx_chat_group`;
CREATETABLE`wx_chat_group`(
`id`int(11) NOTNULLAUTO_INCREMENT,
`name`VARCHAR(64) COLLATEutf8mb4_unicode_ci NOTNULLDEFAULT”,
PRIMARY KEY`id`(`id`)
)
ENGINE= InnoDB
DEFAULTCHARSET= utf8mb4 COLLATEutf8mb4_unicode_ci;
INSERTINTO`wx_chat_group`(`id`, `name`) VALUES(1, ‘德国IT职业信息分享群’);
— 每次抽取的不合规格的昵称将存储如表以供计数
DROPTABLEIFEXISTS`wx_chat_nickname_check`;
CREATETABLE`wx_chat_nickname_check`(
`id`BIGINT(20) NOTNULLAUTO_INCREMENT,
`group_id`int(9) UNSIGNEDNOTNULL,
`wx_puid`VARCHAR(16) COLLATEutf8_unicode_ci NOTNULLDEFAULT”,
`nickname`VARCHAR(64) CHARACTERSETutf8mb4 COLLATEutf8mb4_unicode_ci NOTNULLDEFAULT”,
`create_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPCOMMENT‘Create time’,
PRIMARY KEY`id`(`id`),
INDEX`idx_group_id`(`group_id`),
INDEX`idx_create_time`(`create_time`)
)
ENGINE= InnoDB
DEFAULTCHARSET= utf8mb4 COLLATEutf8mb4_unicode_ci;
2. 用户设置
所有用户自定义变量存入conf文件里,如群名、临时存储路径、数据库接入信息,踢人阈值:
[wechat]
group_name_1=德国IT职业信息分享群
group_id_1=1
path_tmp=/opt/tmp/
notice_random=5
kick_max=10
tuling_api_key=xxxxx
[mysql]
mysql_host=localhost
mysql_port=3306
mysql_user=root
mysql_pwd=xxxx
mysql_database=wechat_group_ibot
3. 监听群消息
初始化群聊对象,并且监听群消息
# 查找群聊,并且设置附加属性,以备后用
def init_group(group_name, group_id):
group = ensure_one(bot.groups.search(group_name))
group.ext_attr = lambda: None
setattr(group.ext_attr, ‘group_id’, group_id)
setattr(group.ext_attr, ‘group_name’, group_name)
returngroup
# 初始化微信机器人bot
bot = Bot(cache_path=True, console_qr=True)
# unique chat person’s id
bot.enable_puid
# 读取自定义参数
cf = configparser.ConfigParser
cf.read(‘wechat.conf’)
group_name_1 = cf.get(‘wechat’, ‘group_name_1’)
group_id_1 = cf.get(‘wechat’, ‘group_id_1’)
# 初始化群聊对象
group_1 = init_group(group_name_1, group_id_1)
# 监听类型为NOTE的群消息,如:”aa”邀请”bbb”加入了群聊
@bot.register(group_1, NOTE)
def welcome_for_group(msg):
try:
new_member_name = re.search(r’邀请”(.+?)”|”(.+?)”通过’, msg.text).group(1)
exceptAttributeError:
return
group_1.send(welcome_text.format(new_member_name, space_after_chat_at))
# 保持bot持续运行
bot.join
4. 昵称检查
检查群友昵称,存入数据库并且发送提醒, 具体逻辑代码这里不予累述。
def check_nickname(nickname):
# 正则检验群昵称是否标准
ifre.match(r'([一-龥]|[ -~]|[sS])+|([一-龥]|[ -~])+|([一-龥]|[ -~])+’, nickname):
returnTrue
else:
returnFalse
……
# 检查群友昵称
def process_group_members(group):
# 每次检查前先刷新群成员信息,避免用户改了昵称后再次被提醒
# 但刷新会改变成员临时的内部puid,所以检查昵称必须同时结合puid和nickname
group.update_group(members_details=False)
……
formember ingroup:
nickname = member.name
wx_puid = member.puid
ifnotcheck_nickname(nickname):
invalid_member = GroupMember(nickname, wx_puid, 0)
invalid_members.append(invalid_member)
…..
# 随机抽取不合格的5人
random_members = random.sample(invalid_members, k=5)
……
# 将本次提醒群友存入数据库,供下次计数
def insert_invalid_name(group_id, wx_puid, nickname):
bot_db.execute(“INSERT INTO wx_chat_nickname_check (`group_id`, `wx_puid`, `nickname`)”
” VALUES (%s, %s, %s)”,
(group_id, wx_puid, nickname))
# 获取昵称不合规群友被提醒计数
def get_invalid_name_count(group_id, wx_puid, nickname):
result = bot_db.get_count(“SELECT id FROM wx_chat_nickname_check “
“WHERE group_id = %s and (wx_puid = %s or nickname = %s)”, (group_id, wx_puid, nickname))
returnresult
5. 数据库连接池
这里的数据库连接使用了数据库连接池:DBUtils.PersistentDB
DBUtils.PooledDB: 适用于多线程频繁开启关闭数据库连接
DBUtils.PersistentDB:适用于单线程多次频繁连接数据库
如果不采用线程池而是采取直连,那么运行一段时间后,脚本将出现该错误
pymysql.err.OperationalError: 2006
这里将DBUtils再次封装了一下,写了一个单例模式BotDatabase, 提供了query(select), execute(update, delete) 以及批处理execute等常用接口。
6. 启动定时器
# 早八点晚八点各执行检查一次
def start_schedule_for_checking_member(group):
scheduler = BlockingScheduler
scheduler.add_job(lambda: process_group_members(group), ‘cron’, hour=8, minute=1, timezone=“Europe/Paris”)
scheduler.add_job(lambda: process_group_members(group), ‘cron’, hour=20, minute=1, timezone=“Europe/Paris”)
最终成果
已知问题
在消息中输入 @群员昵称 并不能真正让该群友收到@提示(显示推送提示),微信App里是在@群员昵称后自动加上了一个特殊的显示空白的字符u’?′。但是经测试,加上这个符 也不行,推测是微信Web API基于防范垃圾推送,屏蔽了群提示接口。
wxpy的bot在运行一段时间后会停止工作,出现连接服务器错误,必须重新登录,推测是微信Web API的Session安全机制导致的问题。
数据清洗
一段时间后大部分群友修改了昵称,于是有了在德中国程序员职业和专业方向的数据,经清洗后,导出CSV规格如下。
数据分析
该任务所需第三方库如下:
pip3 installpandas
pip3 installmatplotlib
pip3 installjieba
pip3 installwordcloud
pip3 installseaborn
pip3 installpalettable
开发需求
1. 在德程序员男女比例,输出Pie Chart
defgen_pie_member_gender(self, csv_file):
df = pd.read_csv(csv_file, delimiter=‘ ‘, encoding=‘utf-8’)
genders = df[‘gender’]
col = [0, 0, 0]
forg ingenders:
ifg == 1:
col[0] = col[0] + 1
elifg == 2:
col[1] = col[1] + 1
else:
col[2] = col[2] + 1
perccent_male = ‘{0:.2f}%’.format((col[0]/len(genders) * 100))
perccent_female = ‘{0:.2f}%’.format((col[1]/len(genders) * 100))
perccent_unknown = ‘{0:.2f}%’.format((col[2]/len(genders) * 100))
labels = [r’Male %s’% perccent_male,
r’Female %s’% perccent_female,
r’Unknown %s’% perccent_unknown]
colors = [‘lightskyblue’, ‘pink’, ‘gold’]
plt.figure(figsize=(8, 6))
patches, texts = plt.pie(col, colors=colors, startangle=90)
plt.legend(patches, labels, loc=“best”)
plt.title(‘Gender of Member’)
# Set aspect ratio to be equal so that pie is drawn as a circle.
plt.axis(‘equal’)
plt.tight_layout
path_image = os.path.join(self.path_analyse,
‘%s_member_gender_pie.png’% self.group_id)
plt.savefig(path_image, format=‘png’, dpi=100)
plt.close
returnpath_image
分析:
在德中国程序猿和程序媛比率约为2:1,这个比例基本和中国籍蓝卡申请人男女比率持平。但是根据2018年中国程序员数据调查表,中国程序员群体中男女比例接近12:1。德国的各位猿,你们就偷乐吧。
2. 在德IT软件专业在职人员和学生比例,输出Pie Chart
代码和上面雷同。
分析:
IT信息行业在职工作人员和在读学生比率为9比1,绝大部分人是在职工作的。
3. 在德程序员所处行业和专业方向,输出词云
# 这里采用一个汉字停词库,近两千词
@staticmethod
defload_stopwords:
filepath = os.path.join(‘./assets’, r’stopwords_cn.txt’)
stopwords = [line.strip forline inopen(filepath, encoding=‘utf-8’).readlines]
returnstopwords
defgen_wordcloud_info_nicknames(self, csv_file, column=‘branch’, gender=‘all’):
df = pd.read_csv(csv_file, delimiter=‘ ‘, encoding=‘utf-8’)
stopwords = set(STOPWORDS)
stopwords.update(self.load_stopwords)
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!