直男日报:
# 记录在一起多少天
# 爬取女朋友所在城市的天气
# 每天给女朋友一句土味情话
# 爬取Bing主页的壁纸,保存到本地并发送
自学爬虫一个月左右,先用R后用python,看了许多帖子也走了不少弯路,目前可以实现R和python的静态网页的抓取,RSelenium的动态网页抓取,分享一下自学的经验。
在看别人的文章常常遇到不懂的名词,我的建议是去把那些出现频率高的术语弄懂,如果能加几个python社群就更好了。
譬如你不懂这里的静态网页和动态网页是什么,而这两个又对这篇文章理解有决定性作用,建议先百度之。学爬虫的过程就像盖一个大楼。粗略的打个比方,一开始考虑甲方的要求是什么,根据这个要求琢磨楼怎么盖,画出来模子再研究这么盖地暖怎么铺、中央空调怎么接,接了管道之后这个大楼还稳不稳?这就要考虑水泥的质量和建材。所以爬虫越到后面,你发现自己的焦点已经从技术变成了网页的基础:html协议的结构,为什么html源码需要解析,js脚本的加载,CSS控制网页布局etc
whatever,学习最好的方式是带着问题学,建议遇到这些问题的时候再去百度。
先讲一下我学爬虫的足迹,看实例的朋友可跳过这一部分
爬虫能做什么:在批量结构类似的网站上爬取指定信息,如豆瓣top250电影的名称、主演、评分.....
→top250爬虫实例,对着实例学习(个人感觉requests包和bs4包眉清目秀,比较适合上手),贴一段我的代码,只爬了第一页
import requests
from bs4 import BeautifulSoupdef 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.textweb = 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_timedays = delta.daysreturn 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_encodingreturn response.textdef 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_dayelse: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:#去掉\nlines.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_urlreturn 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() + 1delta = datetime - 445earthyLove = 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 + 20image = 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找到目标的Usernameitchat.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__':#如果所有模块成功importsend_to_girlfriend('J', 'guangdong')
来一张效果图