自学经验总结+实战:python爬虫的自我修养与每天微信给女朋友发一份直男日报

直男日报:

# 记录在一起多少天

# 爬取女朋友所在城市的天气

# 每天给女朋友一句土味情话

# 爬取Bing主页的壁纸,保存到本地并发送

 

自学爬虫一个月左右,先用R后用python,看了许多帖子也走了不少弯路,目前可以实现R和python的静态网页的抓取,RSelenium的动态网页抓取,分享一下自学的经验。

在看别人的文章常常遇到不懂的名词,我的建议是去把那些出现频率高的术语弄懂,如果能加几个python社群就更好了。

譬如你不懂这里的静态网页和动态网页是什么,而这两个又对这篇文章理解有决定性作用,建议先百度之。学爬虫的过程就像盖一个大楼。粗略的打个比方,一开始考虑甲方的要求是什么,根据这个要求琢磨楼怎么盖,画出来模子再研究这么盖地暖怎么铺、中央空调怎么接,接了管道之后这个大楼还稳不稳?这就要考虑水泥的质量和建材。所以爬虫越到后面,你发现自己的焦点已经从技术变成了网页的基础:html协议的结构,为什么html源码需要解析,js脚本的加载,CSS控制网页布局etc

whatever,学习最好的方式是带着问题学,建议遇到这些问题的时候再去百度。

 

先讲一下我学爬虫的足迹,看实例的朋友可跳过这一部分

爬虫能做什么:在批量结构类似的网站上爬取指定信息,如豆瓣top250电影的名称、主演、评分.....

→top250爬虫实例,对着实例学习(个人感觉requests包和bs4包眉清目秀,比较适合上手),贴一段我的代码,只爬了第一页

import requests
from bs4 import BeautifulSoup

def getHTML(url):
    headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36'}
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
    return response.text

web = getHTML("https://movie.douban.com/top250")
soup = BeautifulSoup(web, 'html.parser')
# CSS选择器,得到标签
name = soup.select('.hd a > span:nth-of-type(1)')
for i in range(len(name)):
    name[i] = name[i].get_text()

→getHTML获得网页源码,那response又是什么呢?请百度:html协议的结构(注意请求头),请求方式(注意get)。改变headers中的’user-agent'是模拟浏览器客户端,如何查自己浏览器的user-agent自行百度。timeout=10是最大请求时间为10s,超过这个时间脚本将不再进行尝试访问。response是服务器的响应,通过属性text获得网页源码,raise_for_status()函数如果访问成功则跳过。

soup = BeautifulSoup(web, 'html.parser')

→对网页源码进行解析,推荐果冻公开课第四课,什么是DOM:https://www.zhihu.com/question/34219998
不同解析器之间速度和兼容性有差别,咱刚入门也不讲究这个,就用了系统自带的html.parser。
为什么爬虫还要对源码进行解析?我的理解是,源码是一堆字符串,解析之后可以建立不同节点之间的联系,让python识别这是html文档,从而可以利用节点之间的树状关系对目标数据进行定位。

# CSS选择器,得到标签
name = soup.select('.hd a span:nth-of-type(1)')

→如何选择目标元素呢?这涉及选择器的问题,一般有xpath和CSS两种选择器,一开始用R的时候上手的是xpath,但select()只支持CSS选择器,这两种选择器在w3school都可以查到,建议学一遍。在这里的意思是:在所有hd的class下的a标签里选择第一个span标签,可见对应的是电影中文标题。

我们查看变量name,是一个list,每个元素是对应的标签

name

# Out
[<span class="title">肖申克的救赎</span>,
 <span class="title">霸王别姬</span>,
 <span class="title">这个杀手不太冷</span>,
 <span class="title">阿甘正传</span>,
 <span class="title">美丽人生</span>,
 <span class="title">千与千寻</span>,
 <span class="title">泰坦尼克号</span>,
 <span class="title">辛德勒的名单</span>,
 <span class="title">盗梦空间</span>,
 <span class="title">忠犬八公的故事</span>,
 <span class="title">机器人总动员</span>,
 <span class="title">三傻大闹宝莱坞</span>,
 <span class="title">放牛班的春天</span>,
 <span class="title">海上钢琴师</span>,
 <span class="title">楚门的世界</span>,
 <span class="title">大话西游之大圣娶亲</span>,
 <span class="title">星际穿越</span>,
 <span class="title">龙猫</span>,
 <span class="title">熔炉</span>,
 <span class="title">教父</span>,
 <span class="title">无间道</span>,
 <span class="title">疯狂动物城</span>,
 <span class="title">当幸福来敲门</span>,
 <span class="title">怦然心动</span>,
 <span class="title">触不可及</span>]

然后提取每个标签的文本信息即可,有许多不同的方式,此处使用get_text()

for i in range(len(name)):
    name[i] = name[i].get_text()

再次查看name

# Out

['肖申克的救赎',
 '霸王别姬',
 '这个杀手不太冷',
 '阿甘正传',
 '美丽人生',
 '千与千寻',
 '泰坦尼克号',
 '辛德勒的名单',
 '盗梦空间',
 '忠犬八公的故事',
 '机器人总动员',
 '三傻大闹宝莱坞',
 '放牛班的春天',
 '海上钢琴师',
 '楚门的世界',
 '大话西游之大圣娶亲',
 '星际穿越',
 '龙猫',
 '熔炉',
 '教父',
 '无间道',
 '疯狂动物城',
 '当幸福来敲门',
 '怦然心动',
 '触不可及']

 

如果之前都好好百度的话,看到这里恭喜你静态网站你几乎都可以爬了,注意在这里我们撇开爬虫的效率和反爬虫不谈,上面谈的希望能提供一点帮助给那些想入门爬虫的朋友。

 

下面开始我们正儿八经的实战项目,每天给女朋友发一份直男日报

需要用到的包有

import itchat #对接微信,用python给女票发消息
import datetime #获取当前日期并进行日期的运算
import os #操作路径的包、
from skimage import io #从url读取图片,以数组格式储存
import matplotlib #在这里用来将数组格式的RGB图片保存成png格式
import re #正则表达式模块
import requests #爬虫请求模块
from bs4 import BeautifulSoup #解析html源码

# 记录在一起多少天

def get_datetime():
    begin_time = datetime.date(2018, 5, 20)
    now = datetime.date.today()
    delta = now - begin_time
    days = delta.days
    return days

日期有datetime.date和datetime.datetime两种对象,datetime格式精确到小时分钟秒,date年月日;两个date对象可以做加减运算;得到timedelta对象,它的属性days可返回int格式的天数

 

# 爬取每天的天气

def get_HTML(url):
    '''获取一个网页,headers模拟浏览器的报头,注意是字典形式和键的名称;raise_for_status()
    如果没有获取成功会返回连接状态;编码方式用响应的apparent_encoding;在查找资源中我们只需要
    在网页源码中找即可,所以返回响应的文本'''
    headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) \
               AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36'}
    response = requests.get(url, headers=headers, timeout=10)
    response.raise_for_status()
    response.encoding = response.apparent_encoding
    return response.text

def get_weather(city):
    '''通过观察网页结构,得到不同天气城市url之间的规律;将html源码解析成DOM树soup,不同的节点开始建立联系;
    利用CSS选择器找到相应的节点提取出标签,get('href'),get_text()得到文本,注意这两个函数都是标签的函数,不是
    列表'''
    url = 'http://www.weather.com.cn/textFC/' + city + '.shtml'
    web = get_HTML(url)
    soup = BeautifulSoup(web, 'html.parser')
    
# 这个网站天气分为白天和夜晚。存储在不同的tag下
    weather_day = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(3)')
    weather_day = weather_day[0].get_text()
    weather_night = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(6)')
    weather_night = weather_night[0].get_text()
    if weather_day == weather_night:
        weather = weather_day
    else:
        weather = weather_day + '转' + weather_night

# 同理温度
    temperature_max = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(5)')
    temperature_max = temperature_max[0].get_text()
    temperature_min = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(8)')
    temperature_min = temperature_min[0].get_text()
    temperature = temperature_max + '°C' + '/' + temperature_min + '°C'

# 得到风力和风速
    wind_direction = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(4) span:nth-of-type(1)')
    wind_direction = wind_direction[0].get_text()
    wind_force = soup.select('.conMidtab3 table tr:nth-of-type(1) td:nth-of-type(4) span:nth-of-type(2)')
    wind_force = wind_force[0].get_text()
    wind = {'wind_direction': wind_direction, 'wind_force': wind_force}
    
    w = {'weather':weather, 'temperature':temperature, 'wind':wind}
    
    return w

 

# 每天一句不同的土味情话,从网上把搜集好的土味情话打包放到txt文件里,为方便代码阅读贴一下

def get_earthyLove(delta):
    filename = 'earthyLove.txt'
    with open(filename, 'r') as f:
        lines = []
        org_lines = f.readlines()
        for ln in org_lines:
            if len(ln) != 1:#去掉\n
                lines.append(ln)
        line = lines[delta]
    return line.split('、')[1]#去掉序号和顿号

将txt文本进行处理:去掉读入列表里的\n;去除序号和顿号;在这里去除顿号的方法使用的是每一行前几位是数字,事实上用split函数以顿号为分隔符能达到同样的效果。

 

#爬取每天的Bing壁纸

有很多写相关的博客但大多对初学者不怎么友好,要么写的很玄学要么就是太讲究,一开始研究了两天正则表达式,终于渡劫爬到了壁纸的url,以为壁纸是动态加载的大赞微软nb,后来大佬告诉我正则表达式能找到的网页源码里肯定也有,我横着看竖着看终于发现url在<head>里,只不过<body>里没有罢了...但还是建议两种方法都掌握一下

def get_jpg(url):
    html = get_HTML(url)   
# html解析
    soup = BeautifulSoup(html, 'html.parser')
    jpg_url = soup.select('#bgLink')[0].get('href') #get()获取标签属性,get_text()获取文本
    img_url = url + jpg_url
    return img_url
# =============================================================================
# 正则表达式匹配,网上讲正则的很多,建议廖雪峰老师的正则表达式和菜鸟教程对照着看,然后做一下廖雪峰老师的课后题
#     re_jpg = re.compile(r'url:.{10,90}jpg')
#     jpglist = re.findall(re_jpg, html)
#     if jpglist:
#         jpg_url = jpglist[1].split('/')[1]
#         image_url = url + jpg_url
#         return image_url
#     else:
#         print('failed')
# =============================================================================

def get_img():
    '''这里用到skimage里的io.imread读取来自网页的jpg图片,尝试用pyplot.imread貌似只能读取png格式的文件'''
    url = 'https://cn.bing.com'
    img_url = get_jpg(url)
    img = io.imread(img_url)
    return img

到这里所有的准备工作就已经完成了,剩下的就是利用itchat发消息了

def send_to_girlfriend(name, city):
    '''将所有函数打包,修改工作路径到earthy.txt所在路径'''
    os.chdir('C:\\Users\\mac\\Desktop\\cufe\\self\\program_learning\wxRobot')
    
# 以2018.05.20为第一天,具体的+1和-445与代码逻辑无关,获取各变量
    datetime = get_datetime() + 1
    delta = datetime - 445
    earthyLove = get_earthyLove(delta)
    w = get_weather(city)
    weather = w['weather']
    tem = w['temperature']
    wind_force = w['wind']['wind_force']
    wind_direction = w['wind']['wind_direction']
    
# 搭建城市天气url时需要拼音,而在发消息的时候需要用中文,利用字典实现
    location = {'hongkong':'香港', 'beijing':'北京', 'guangdong':'广州', 'chongqing':'重庆'}
    city = location[city]

# 构建f-string,格式化字符串 
    msg_org = f'今天是七夕,我是XX给你准备的礼物!'
    msg_org_datetime= f'你知道嘛,我们已经在一起{datetime}天啦!'
    msg_datetime = f'我们已经在一起{datetime}天了噢!'
    if wind_direction == wind_force:#有时候天气网站的风向和风力都一样
        msg_weather = f'下面是rkun的天气预报:\n今天{city}{weather}\n气温:{tem}\n{wind_direction}'
    else:
        msg_weather = f'下面是rkun的天气预报:\n今天{city}{weather}\n气温:{tem}\n{wind_direction}:{wind_force}'
    
    msg_earthyLove = f'{earthyLove}'
    
# itchat只能发送本地图片,无法将数组形式的图片发送,故先用matplotlib.image.imsave储存到本地,这个函数的
# 优点在于可以通过定义存储路径的后缀改变图片格式,同时可以将数组形式的图片保存成图像格式
    wallpaper_index = delta + 20
    image = get_img()
    file_path = 'C:\\Users\\mac\\Desktop\\wallpaper\\th(' + str(wallpaper_index) + ').png'
    matplotlib.image.imsave(file_path, image)

# 模拟登陆网页微信,nickName是暱称,remarkName是备注,每次登陆网页微信的每个好友的UserName是随机分配的,
# itchat.send_msg()的第一个参数为目标的Username,这既不是备注也不是暱称,而是每次登陆随机分配的,所以需要先用search_friends找到目标的Username
    itchat.auto_login(hotReload=True)
    whom = itchat.search_friends(nickName=name)[0]['UserName']

    if delta == 0:#下面的ifelse语句纯粹是为了让七夕和其他日子发的消息不一样,直接看else就好
        itchat.send_msg(msg_org, whom)
        itchat.send_msg(msg_org_datetime, whom)
        itchat.send_msg(msg_weather, whom)
        itchat.send_msg(msg_earthyLove, whom)
    else:
        itchat.send_msg(msg_datetime, whom) 
        itchat.send_msg(msg_weather, whom)
        itchat.send_msg(msg_earthyLove, whom)
        itchat.send_image(file_path, whom)

if __name__ == '__main__':#如果所有模块成功import
    send_to_girlfriend('J', 'guangdong') 

来一张效果图

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章