0. 引言
介绍下 Python 用 Beautiful Soup 周期性爬取 xxx 网站获取新闻流;
图 1 项目介绍
1. 开发环境
Python: 3.6.3
BeautifulSoup: 4.2.0 , 是一个可以从HTML或XML文件中提取数据的Python库*
( BeautifulSoup 的中文官方文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc.zh/ )
2. 介绍
首先需要知道什么是 HTML ( Hypertext Markup Language,超文本标记语言 ) :
HTML 是用来描述网页的一种语言*:
- HTML 指的是超文本标记语言 (Hyper Text Markup Language)
- HTML 不是一种编程语言,而是一种标记语言 (markup language)
- 标记语言是一套标记标签 (markup tag)
- HTML 使用标记标签来描述网页
代码实现主要分为三个模块:
1. 计时 / second cnt
因为是周期性爬取,所以需要计时器来控制;
2. 设置代理 / set proxy
为了应对网站的反爬虫机制,需要切换代理;
3. 爬虫 / web spider
利用 requests 请求网站内容,然后 beautifulsoup 提取出所需信息;
利用 requests.get() 向服务器发送请求 >>> 拿到 html 内容 >>> 交由 beautifulsoup 用来解析提取数据;
先看一个 requests 和 beautifulsoup 爬虫的简单例子:
requests :
>>> requests.get(html, headers=headers, proxies=proxies)
>>> # headers 和 proxies 是可选项 / optional
发送请求给网站,然后拿到返回的 HTML 内容,转换为 beautifulsoup 的对象,bs负责去提取数据;
from bs4 import BeautifulSoup
import requests
html = "https://www.xxx.com"
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
}
proxies = "114.231.xx.xx:xxxx" resp = requests.get(html, headers=headers, proxies=proxies)
resp.encoding = 'utf-8' bsObj = BeautifulSoup(resp.content, "lxml")
本文以爬取 https://www.ithome.com/blog/ 为例,把页面上的新闻爬取下来,过滤掉多余信息,拿到 新闻时间 & 新闻标题 ;
图 2 网站界面
拿到网站的 html 代码之后,如何剥离出自己想要的数据,参考图 3 的流程:
图 3 从 html 中 剥离数据的流程
首先看看 bsObj = BeautifulSoup(resp.content, "lxml") 拿到的 目标网站 ( https://www.ithome.com/blog/ ) 的 HTML 代码,这个HTML代码也就是浏览器打开网页然后F12得到的当前网页的代码;
HTML代码描述了网页的排版布局文本图片等内容,浏览器就是提供渲染HTML代码的功能;
拿到的 xxxx.com 的网页HTML代码:
...
<div class="block">
<h2>
<a href="https://www.ithome.com/html/android/380353.htm" target="_blank">魅族黄章评价滑盖智能手机:穷途末路倒退的设计,中看不中用</a><span class="state tody">今日 14:53</span></h2>
<div class="memo">
<p>昨晚荣耀Magic2、小米MIX 3手机相继亮相,并且都采用了升降式或者滑盖式屏幕设计。刚刚魅族黄章对于滑盖设计表达了自己的看法:滑盖可以说是穷途末路倒退的设计。</p>
</div>
</div>
</li>
<li>
<a class="list_thumbnail" href="https://www.ithome.com/html/discovery/380352.htm" target="_blank">
<img src="//img.ithome.com/newsuploadfiles/thumbnail/2018/8/380352_240.jpg"/>
</a>
<div class="block">
<h2>
<a href="https://www.ithome.com/html/discovery/380352.htm" target="_blank">俄罗斯计划“世界级”研究中心,拟克隆猛犸象</a><span class="state tody">今日 14:48</span></h2>
<div class="memo">
<p>俄罗斯科学家计划用保存的冰河时代遗骸的DNA,在一个耗资450万英镑的新侏罗纪公园中心克隆猛犸象</p>
</div>
</div>
</li>
<li>
<a class="list_thumbnail" href="https://lapin.ithome.com/html/digi/380351.htm" target="_blank">
<img src="//img.ithome.com/newsuploadfiles/thumbnail/2018/8/380351_240.jpg"/>
</a>
<div class="block">
<h2>
...
然后我们就需要在这 HTML 代码中借助 find() 函数 提取出我们所需要的数据;
关于 find() 函数的参数:
# Author: coneypo # 返回查找到的内容
find(tag=None, attrs={}, recursive=True, text=None, **kwargs) # 返回查找到的内容列表
findAll(tag=None, attrs={}, recursive=True, text=None, limit=None, **kwargs) # tag 标签参数 tag='p'
# attrs 属性参数 {'href':"www.xxx.com"}
# recursive 递归参数 True: 递归向下查询/ False: 只查询一级标签
# text 文本参数 text="hello world"
# limit 范围限制参数 保留查询结果的前n项 / findall(limit=1) 等于 find()
# kwargs 关键词参数 id="header1"
关于 find() 函数的使用:
# 1. find(tagname) # 查找名为 <p> 的标签
find('p') # 2. find(list) # 查找列表中的所有标签,<p>,<body>
find(['p', 'body']) # 3. find(dict) # 查找字典中标签名和值匹配的标签 <a href="www.xx.com" target="_blank">
find({'target': "_blank", 'href': "wwww.xx.com"}) # 4. find('id'='xxx') # 寻找id属性为xxx的<h1 id="myHeader">Hello World!</h1>
find('id'='myHeader')
图 4 find() 函数的匹配
接下来就是利用 find() 一层层筛选 HTML 代码;
首先定位到 <div class="block" >;
>>> block = bsObj.find_all("div", {"class": "block"})
<div class="block">
<h2>
<a href="https://www.ithome.com/html/android/380353.htm" target="_blank"> 魅族黄章评价滑盖智能手机:穷途末路倒退的设计,中看不中用</a>
<span class="state tody">今日 14:53</span>
</h2>
<div class="memo">
<p>昨晚荣耀Magic2、小米MIX 3手机相继亮相,并且都采用了升降式或者滑盖式屏幕设计。刚刚魅族黄章对于滑盖设计表达了自己的看法:滑盖可以说是穷途末路倒退的设计。</p>
</div>
接下来就是要提取出 新闻标题 title 和 新闻时间 time;
注意 “block” 是用 find_all() 拿到的,返回的是一个 list 列表,遍历这个列表,去找 title & time;
对于单个的 "block",比如 block[i]:
提取新闻时间:
>>> block[i].find('span', {'class': "state tody"})
<span class="state tody">今日 14:53</span>
然后
>> block[i].find('span', {'class': "state tody"}).get_text()
得到 time 内容:
今日 14:53
提取新闻标题:
>>> block[i].find('a', {'target': "_blank"})
<a href="https://www.ithome.com/html/android/380353.htm" target="_blank">魅族黄章评价滑盖智能手机:穷途末路倒退的设计,中看不中用</a>
然后
>> block[i].find('a', {'target': "_blank"}).get_text()
得到 title 内容:
魅族黄章评价滑盖智能手机:穷途末路倒退的设计,中看不中用
这样就可以拿到了 新闻标题 titile 和 新闻时间 time了;
3. 代码实现
1. 计时器
这里是一个秒数计数功能模块,利用 datetime 这个库,获取当前时间的秒位,和开始时间的秒位进行比对,如果发生变化,sec_cnt 就 +=1;
sec_cnt 输出 1,2,3,4,5,6...是记录的秒数,可以由此控制抓包时间;
>>> time = datetime.datetime.now() # 获取当前的时间,比如:2018-08-31 14:32:54.440831
>>> time.second() # 获取时间的秒位
second_cnt.py:
# Author: coneypo
# Created: 08.31 import datetime # 开始时间
start_time = datetime.datetime.now()
tmp = 0
# 记录的秒数
sec_cnt = 0 while 1:
current_time = datetime.datetime.now() # second 是以60为周期
# 将开始时间的秒 second / 当前时间的秒 second 进行对比;
# 如果发生变化则 sec_cnt+=1;
if current_time.second >= start_time.second:
if tmp != current_time.second - start_time.second:
# print("<no 60>+ ", tmp)
sec_cnt += 1
print("Time_cnt:", sec_cnt)
# 以 10s 为周期
if sec_cnt % 10 == 0:
# do something
pass
tmp = current_time.second - start_time.second # when get 60
else:
if tmp != current_time.second + 60 - start_time.second:
sec_cnt += 1
print("Time_cnt:", sec_cnt) # 比如以 10s 为周期
if sec_cnt % 10 == 0:
# do something
pass
tmp = current_time.second + 60 - start_time.second
2. set_proxy / 设置代理
这个网站可以提供一些国内的代理地址: http://www.xicidaili.com/nn/ ;
因为现在网站可能会有反爬虫机制,如果同一个 IP 如果短时间内大量访问该站点,就会被拦截;
所以我们需要设置代理,爬这个代理网站页面,拿到的 proxy 地址传,给 requests.get 的 proxies 参数:
>>> requests.get(html, headers=headers, proxies=proxies)
两个函数:
get_proxy(): 从代理网站爬取代理地址,存入本地的"proxies.csv"中;
get_random_proxy(): 从本地的"proxies.csv"中,随机选取一个代理proxy;
注意,xicidali.com也有反爬虫机制,亲身体验短时间大量爬数据ip会被block,而且我们一次爬取可以拿100个代理地址,所以只需要爬一次代理网站就行了,存到本地的csv中;
要代理从本地的csv中随机获取proxy地址;
csv可以定期的用 “get_proxy()” 进行更新;
get_proxy.py :
# Author: coneypo
# Created: 08.31
# Updated: 09.01
# set proxy
# save to csv from bs4 import BeautifulSoup
import requests
import csv
import pandas as pd
import random path_proxy_csv = "proxies.csv" # 从 xicidaili.com 爬取代理地址,存入本地csv中
def get_proxy():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
}
proxy_url = 'http://www.xicidaili.com/nn/'
resp = requests.get(proxy_url, headers=headers)
soup = BeautifulSoup(resp.text, 'lxml')
ips = soup.find_all('tr')
proxy_list = []
for i in range(1, len(ips)):
ip_info = ips[i]
tds = ip_info.find_all('td')
proxy_list.append(tds[1].text + ':' + tds[2].text) if len(proxy_list) == 0:
print("可能被屏蔽了")
else:
print("拿到的代理个数:", len(proxy_list))
with open(path_proxy_csv, 'w', newline="") as csvfile:
writer = csv.writer(csvfile)
for i in range(len(proxy_list)):
print(proxy_list[i])
writer.writerow([proxy_list[i]]) # get_proxy() # 从代理的 csv 中随机获取一个代理
def get_random_proxy():
column_names = ["proxy"]
proxy_csv = pd.read_csv("proxies.csv", names=column_names) # 新建一个列表用来存储代理地址
proxy_arr = [] for i in range(len(proxy_csv["proxy"])):
proxy_arr.append(proxy_csv["proxy"][i]) # 随机选择一个代理
random_proxy = proxy_arr[random.randint(0, len(proxy_arr) - 1)] print(random_proxy)
return random_proxy # get_random_proxy()
3. get_news_from_xxx / 爬虫
由计时器的 sec_cnt 控制爬虫的周期 >> 每次先去 “proxies.csv” 拿代理 >> 然后交给 requests 去拿数据 >> 拿到 HTML 之后交给 beautiful 对象 >> 然后过滤出新闻流:
伪代码如下:
while 1:
if 周期到了:
获取代理地址;
requests 给目标网站发送请求;
获取到的HTML内容交给BeautifulSoup:
过滤出新闻标题和时间;
get_news_from_xxx.py 完整的代码:
# Author: coneypo
# Created: 08.31
# Updated: 09.01
# web spider for xxx.com from bs4 import BeautifulSoup
import requests
import random
import datetime
import csv
import pandas as pd headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
} # 从 xicidaili.com 爬取代理地址,存入本地csv中
def get_proxy():
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36'
}
proxy_url = 'http://www.xicidaili.com/nn/'
resp = requests.get(proxy_url, headers=headers)
soup = BeautifulSoup(resp.text, 'lxml')
ips = soup.find_all('tr')
proxy_list = []
for i in range(1, len(ips)):
ip_info = ips[i]
tds = ip_info.find_all('td')
proxy_list.append(tds[1].text + ':' + tds[2].text) if len(proxy_list) == 0:
print("可能被代理网站屏蔽了", '\n')
else:
print("拿到的代理个数:", len(proxy_list), '\n')
with open("proxies.csv", 'w', newline="") as csvfile:
writer = csv.writer(csvfile)
for i in range(len(proxy_list)):
# print(proxy_list[i])
writer.writerow([proxy_list[i]]) # 执行一次就可以了;
# 执行一次可以更新本地代理地址;
get_proxy() # 从存放代理的 csv 中随机获取一个代理
def get_random_proxy():
column_names = ["proxy"]
proxy_csv = pd.read_csv("proxies.csv", names=column_names) # 新建一个列表用来存储代理地址
proxy_arr = [] for i in range(len(proxy_csv["proxy"])):
proxy_arr.append(proxy_csv["proxy"][i]) # 随机选择一个代理
random_proxy = proxy_arr[random.randint(0, len(proxy_arr)-1)] print("当前代理:", random_proxy)
return random_proxy # 爬取网站
def get_news():
html = "https://www.ithome.com/blog/"
resp = requests.get(html, headers=headers, proxies=get_random_proxy())
resp.encoding = 'utf-8' # 网页内容
bsObj = BeautifulSoup(resp.content, "lxml")
# print(bsObj) # 分析
block = bsObj.find_all("div", {"class": "block"})
# print(block) current_titles = []
current_times = [] # analysis every block
for i in range(len(block)):
# get Time
time_classes = ["state tody", "state other"]
for time_class in time_classes:
tmp = block[i].find_all('span', {'class': time_class})
if tmp:
current_time = (block[i].find('span', {'class': time_class})).get_text()
current_times.append(current_time) # get Title
if block[i].find_all('a', {'target': "_blank"}):
current_title = (block[i].find('a', {'target': "_blank"})).get_text()
current_titles.append(current_title) for i in range(len(current_times)):
print(current_times[i], current_titles[i])
return current_times, current_titles get_news() # 计时
start_time = datetime.datetime.now()
tmp = 0
sec_cnt = 0 while 1:
current_time = datetime.datetime.now() # second 是以60为周期
# 将开始时间的秒second / 当前时间的秒second 进行对比
if current_time.second >= start_time.second:
if tmp != current_time.second - start_time.second:
# print("<no 60>+ ", tmp)
sec_cnt += 1
print("Time_cnt:", sec_cnt)
if sec_cnt % 10 == 0:
get_news()
print('\n')
tmp = current_time.second - start_time.second # when get 60
else:
if tmp != current_time.second + 60 - start_time.second:
sec_cnt += 1
print("Time_cnt:", sec_cnt) if sec_cnt % 10 == 0:
get_news()
print('\n')
tmp = current_time.second + 60 - start_time.second
最终的输出 log:
拿到的代理个数: 100 当前代理: 111.155.124.73:8123
今日 13:53 你应该跳过三星Galaxy Note 9等待S10的5个理由
今日 13:49 索尼CEO回应联机争议:PS上最好玩,为何要跨平台?
今日 13:43 顺丰冷运/买6送4,阳澄湖鲜活六月黄大闸蟹10只旗舰店58元
今日 13:35 华为Mate 9、P10系列GPU Turbo升级已全面开放
今日 13:35 自如熊林:没有意愿“对付”谁,是自如的责任定承担
今日 13:33 《暗黑4》?暴雪尚有一个未公布的3A级动作游戏
今日 13:12 经典重生:Palm新Logo曝光
今日 13:07 安卓系统各版本最新份额:“奥利奥”接近15%,6.0、7.0占大头
今日 12:49 新研究称:世界低估了中国对全球科学的贡献
今日 12:48 韩春雨接受校方调查结果:论文存缺陷,研究过程不严谨
08月31日 中粮出品/宁夏中卫原产,悦活优选100枸杞蜂蜜454g×2瓶39.9元
今日 12:35 手机QQ iOS版v7.7.8正式版更新:新增多款高颜值滤镜
今日 12:23 网游总量调控,游戏暴利时代或终结
今日 12:12 直降30元,网易云音乐氧气耳机开学季大促99元
今日 12:08 美团点评IPO定价区间敲定:每股60-72港币
今日 12:05 索尼HX99、HX95卡片相机发布:大变焦、能拍4K视频
今日 11:30 自如回应“阿里员工租甲醛房去世”:需客观全面调查
今日 11:30 夏末清仓,森马企业店纯棉印花T恤19.9元包邮
今日 11:27 1198元!vivo Y81s正式开售:千元机也有高颜值
今日 11:24 猪的器官能移植给人类吗?科学家研究出了测试程序
今日 11:16 小米8/MIX 2S开始推送MIUI 10稳定版:全新系统UI
今日 11:11 2030年8亿人将被机器人取代,但这些职业竟还能保住饭碗!
08月31日 直降400元,汪峰FIIL随身星Pro降噪耳机799元新低开抢
今日 11:01 生存游戏《人渣》现纳粹纹身引批评,开发商移除并致歉
今日 10:56 雷柏外设开学季大促:机械键盘低至94元
今日 10:46 苹果AR开发的下一个动作:Maps地图应用
今日 10:46 屏幕边框已死,笔记本设计迎来新时代
今日 10:45 可口可乐CEO喝够了可乐,于是51亿美元买下COSTA咖啡
今日 10:41 放弃悟空问答,今日头条的第一次战略性撤退
今日 10:40 狂甩不掉,ZTM M8防水运动蓝牙耳机旗舰店29.9元(40元券)
今日 10:36 MagicScroll可变形平板电脑问世:采用卷轴式柔性屏
今日 10:34 等谷歌牌手表的可以散了,Pixel Watch今年不会有
今日 10:27 佳能全幅无反相机EOS R规格、照片全泄露了
今日 10:07 IFA2018:主打设计!微星发布P65 Creator笔记本电脑
今日 10:01 翻版“药神”:代购印度抗癌药后加价销售,12人被判刑
今日 9:56 359元, 西部数据My Passport 1T移动硬盘开学季大促新低
今日 9:43 媒体:ofo已拖欠云鸟、德邦等多家物流供应商亿元欠款
今日 9:40 巴菲特:苹果若收购特斯拉,我会支持
08月31日 双星旗舰店网面运动鞋69→39元、啄木鸟弹力牛仔裤109→59元
今日 9:31 NASA称国际空间站打补丁成功,气压保持稳定
今日 9:29 疑似华为Mate 20 Pro真机现身!三摄设计大变样
今日 9:25 一波曝光之后,谷歌Pixel 3/3 XL已现身FCC网站
今日 9:24 《赛博朋克2077》可独行出击可双人行动,还可以买房
今日 9:19 2899元,索尼 PlayStation 4 Pro 游戏主机秒杀新低
今日 9:17 河北科大:未发现韩春雨团队主观造假
今日 9:12 米粉平均月收入7485元?网友:算上雷军了吧
08月28日 3000mAh大容量,耐时旗舰店8节5号/7号锂铁电池19.9元
今日 8:56 网传顺丰3亿条数据在暗网出售,官方回应:非顺丰数据
今日 8:49 出版署严控网游总量,腾讯网易股价大跌
今日 8:46 为什么接你电话的客服总是解决不了问题?
Time_cnt: 1
Time_cnt: 2
Time_cnt: 3
Time_cnt: 4
Time_cnt: 5
Time_cnt: 6
Time_cnt: 7
Time_cnt: 8
...
你就可以每隔 10s 刷新下该网站上面的新闻流;
刷新周期在 line 87:
>>> if sec_cnt % 10 == 0:
# 请尊重他人劳动成果,转载或者使用源码请注明出处:http://www.cnblogs.com/AdaminXie
# 项目源码上传到了我的 GitHub,如果对您有帮助,欢迎 Star 支持下:https://github.com/coneypo/Web_Spider
# 请不要利用爬虫从事恶意攻击或者违法活动