贴吧案例
今天我们来爬取一下百度贴吧的数据,
主要步骤为以下五步
1.查看页面是否为静态页面
2.找到url规律
3.获取网页内容
4提取所需数据
5.保存(数据库、本地)
查看是否为静态页面
在这里提一点,目前我们抓取的数据都是静态页面的,所有第一件事就是要确认你所抓取的页面是否为静态 的,这点你可以通过查看网页源代码查看,你可以找到
这是贴吧的页面
这是源代码
通过上图对比我们可以看到,像我们看到的一些标题之类的在源代码中都有体现,我们可以进行抓取
下面我们再来看一个动态加载的页面![
这是腾讯招聘 我搜索Python的一个页面,上面有职位信息,地点等,我们再来看看它的源代码
从图中可以看到,源代码中并不存在招聘岗位等信息,因为此类页面是动态加载的,需要通过动态加载的办法去抓取,这个先说到这儿,我们进行抓取一定要先去看看网页源代码里是静态还是动态,页面中是否有我们需要的东西,你也可以自行查看一下其中的规律
下面我们开始百度贴吧的抓取
我们进行抓取的第一步是确定我们抓取的页面,以及页面是否存在我们需要的信息
第二步就是找到url规律,即你所抓取的地址。
找到url规律
我们在贴吧里搜索英雄联盟吧
可以看到这样一个页面
注意看它的url
kw是关键词IE是编码格式还有一个pn我们点击下一页到第二页看一下有什么不同
此时的pn变成了50,我们再看一下第三页,发现pn变成了100,即pn就是页码以50递进,这样我们确定了url的规律
第一页 https://tieba.baidu.com/f?kw=?&pn=0
第二页 https://tieba.baidu.com/f?kw=?&pn=50
第三页 https://tieba.baidu.com/f?kw=?&pn=100
第n页 pn=(n-1)*50
其中ie被我省略了 ,访问可以得到同样效果
获取页面
我们直接上代码
from urllib import request, parse
import random
import time
class TiebaSpider(object):
def __init__(self):
self.url = 'https://tieba.baidu.com/f?kw={}&pn={}'
self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) '
'Chrome/14.0.835.163 Safari/535.1'}
# 获取响应内容
def get_page(self, url):
req = request.Request(url=url, headers=self.headers)
res = request.urlopen(req)
html = res.read().decode()
return html
# 提取数据
def parse_page(self):
pass
# 保存
def write_page(self,filename, html):
with open(filename, 'w',encoding='utf-8') as f:
f.write(html)
# 入口函数
def run(self):
name = input('请输入贴吧名:')
start = int(input('请输入起始页:'))
end = int(input('请输入终止页:'))
kw = parse.quote(name)
# 拼接+获取内容+保存
for i in range(start, end + 1):
pn = (i - 1) * 50
url = self.url.format(kw, pn)
html = self.get_page(url)
filename = '{}-第{}页.html'.format(name, i)
self.write_page(filename, html)
print('第%d页抓取成功' % i)
# 每爬取一个页面随机休眠1-3秒
time.sleep(random.randint(1, 3))
if __name__ == '__main__':
spider = TiebaSpider()
spider.run()
这个流程其实和我在第一篇中所写没有太大区别,只是让代码更加规范化,实际上的原理是一样的,这里就不一一细说,有不明白的可以回去看看第一篇入门,这里我们使用sleep每抓取一个页面停留一个随机时间,这样做的原因是为了防止被网页封禁,为什么不使用一个固定的时间,是因为人访问页面不可能做到那么精确,当然嫌慢的同学也可以把这个去掉,其中关于数据提取,我们后面再说,在这里,输入你所要爬取的贴吧名以及爬取页数,就可以得到相应的网页
如图,我抓取的是英雄联盟吧1到3页的内容
下面我们做一下优化
首先是User_Agent,在第一篇入门中我们有说到userAgent的作用,这里我们对它再改善,如果我们使用一个user-agent大量访问一个网站,会引起网站的注意,为了更好的伪装,我们新建一个useragents的py文件
在网上找到不同的user-agent放到里面,然后把这个文件导入到刚才所写的程序中,就可以实现以不同的useragent访问网页,
修改后的代码如下
from urllib import request, parse
import random
import time
from useragerts import ua_list
class TiebaSpider(object):
def __init__(self):
self.url = 'https://tieba.baidu.com/f?kw={}&pn={}'
# 获取响应内容
def get_page(self, url):
headers = {'User-Agent': random.choice(ua_list)}
req = request.Request(url=url, headers=headers)
res = request.urlopen(req)
html = res.read().decode()
return html
# 提取数据
def parse_page(self):
pass
# 保存
def write_page(self, filename, html):
with open(filename, 'w', encoding='utf-8') as f:
f.write(html)
# 入口函数
def run(self):
name = input('请输入贴吧名:')
start = int(input('请输入起始页:'))
end = int(input('请输入终止页:'))
kw = parse.quote(name)
# 拼接+获取内容+保存
for i in range(start, end + 1):
pn = (i - 1) * 50
url = self.url.format(kw, pn)
html = self.get_page(url)
filename = '{}-第{}页.html'.format(name, i)
self.write_page(filename, html)
print('第%d页抓取成功' % i)
# 每爬取一个页面随机休眠1-3秒
time.sleep(random.randint(1, 3))
if __name__ == '__main__':
begin = time.time()
spider = TiebaSpider()
spider.run()
stop = time.time()
print('执行时间:%2.f' % (stop - begin))
Python有一个专门的模块用来获取user-agent fake_useragent 需要装这个插件来使用,这个有一个弊端,它是从网站上获取userAgent的如果网络不好或者其他原因可能出现一个问题,装插件的命令如下
在控制台输入一下命令
python -m pip install fake_useragent
模块就成功获取 了
使用也很简单,导入一个,模块,创建一个对象,然后随机获取就可以使用了
第一次可能有点慢 ,还可能报错,可以多试几次,成功后就可以快速获取了 如下图
改动后代码如下
from urllib import request, parse
import random
import time
from fake_useragent import UserAgent
class TiebaSpider(object):
def __init__(self):
self.url = 'https://tieba.baidu.com/f?kw={}&pn={}'
# 生成随机UserAgent
def get_headers(self):
ua = UserAgent()
headers = {'User_Agent': ua.random}
return headers
# 获取响应内容
def get_page(self, url):
headers = self.get_headers()
req = request.Request(url=url, headers=headers)
res = request.urlopen(req)
html = res.read().decode()
return html
# 提取数据
def parse_page(self):
pass
# 保存
def write_page(self, filename, html):
with open(filename, 'w', encoding='utf-8') as f:
f.write(html)
# 入口函数
def run(self):
name = input('请输入贴吧名:')
start = int(input('请输入起始页:'))
end = int(input('请输入终止页:'))
kw = parse.quote(name)
# 拼接+获取内容+保存
for i in range(start, end + 1):
pn = (i - 1) * 50
url = self.url.format(kw, pn)
html = self.get_page(url)
filename = '{}-第{}页.html'.format(name, i)
self.write_page(filename, html)
print('第%d页抓取成功' % i)
# 每爬取一个页面随机休眠1-3秒
time.sleep(random.randint(1, 3))
if __name__ == '__main__':
begin = time.time()
spider = TiebaSpider()
spider.run()
stop = time.time()
print('执行时间:%2.f' % (stop - begin))
这样就成功改造了,
在这里提一下,在爬取过程中,你可能会遇到这样的错误
Connection Reset error [error 104]:
这是远程主机怀疑你是爬虫,断掉了和你的连接,这里我们可以通过以下方式处理,我们捕获异常,连接失败再次连接,具体代码如下
如果报错就休眠0.5秒重试
关于这个模块仔细研究以下,后面其实大致流程也是这样,打好基础
正则解析模块
正则是我们学习爬虫必须要了解的一个模块,我们提取我们所需的数据是要用到
这里我们使用的是re模块
我们先做一个简单的练习
import re
r_list = re.findall('AB', 'ABCABCABCD')
print(r_list)
可以得到结果
还有另外一种方式
import re
parttern = re.compile('AB')
r_list2 = parttern.findall('ABCABCABCD')
print(r_list2)
用一个变量去接收,效果是一样的
总结下来是这样
r_list = re.findall('正则表达式', html,re.S)
parttern = re.compile('正则表达式')
r_list2 = parttern.findall(html,re.S)
其中re.S的作用是一旦正则表达式中出现 . 就无法匹配 /n,让点能匹配/n 就这一个作用
在字符串a中,包含换行符\n,在这种情况下:
如果不使用re.S参数,则只在每一行内进行匹配,如果一行没有,就换下一行重新开始。
而使用re.S参数以后,正则表达式会将这个字符串作为一个整体,在整体中进行匹配。
正则表达式元字符
这里并非所有的 只是一些常用的
匹配任意字符的正则表达式
re.compile('.', re.S)
re.compile('[/s/S]')
贪婪匹配和非贪婪匹配
这是贪婪和非贪婪的表示方式,就这样可能很难理解我们来个例子
import re
html = '''
<div><p>长风破浪会有时</p><div>
<div><p>直挂云帆济沧海</p><div>
'''
# 贪婪匹配
pattern = re.compile('<div><p>.*</p><div>', re.S)
r_lsit = pattern.findall(html)
print(r_lsit)
我们自己写一个假网页进行匹配,看结果
我们只得到一个元素 ,即中间的都被匹配进去了
我们再来看非贪匹配
import re
html = '''
<div><p>长风破浪会有时</p><div>
<div><p>直挂云帆济沧海</p><div>
'''
# 非贪婪匹配
pattern = re.compile('<div><p>.*?</p><div>', re.S)
r_lsit2 = pattern.findall(html)
print(r_lsit2)
匹配最近的一个元素,在实际使用中更多的是用到非贪婪匹配,我们可以提取出一个一个的元素,而非贪婪就相当于吧符合的整个一块提取了
那我们只想要其中的文字,不需要外面的《div》之类的怎么办,很简单加一个() 看代码
import re
html = '''
<div><p>长风破浪会有时</p><div>
<div><p>直挂云帆济沧海</p><div>
'''
# 贪婪匹配
pattern = re.compile('<div><p>(.*)</p><div>', re.S)
r_lsit = pattern.findall(html)
print(r_lsit)
# 非贪婪匹配
pattern = re.compile('<div><p>(.*?)</p><div>', re.S)
r_lsit2 = pattern.findall(html)
print(r_lsit2)
得到结果如下
第一个是贪婪匹配
第二个是非贪婪匹配
爬虫里面基本上不使用贪婪
正则表达式分组
在完整的模式中定义子模式,将每个圆括号中子模式匹配出来的结果提取出来
是什么意思呢 我们看下面这段代码
import re
s = 'A B C D'
p1 = re.compile('\w+\s+\w')
print(p1.findall(s))
# 匹配完整 ->['A B', 'C D']
p2 = re.compile('(\w)+\s+\w')
print(p2.findall(s))
# 第一步匹配完整 ->['A B', 'C D']
# 第二步匹配()->['A', 'C']
p3 = re.compile('(\w)+\s+(\w)')
print(p3.findall(s))
# 第一步匹配完整 ->['A B', 'C D']
# 第二步匹配()->[('A', 'B'), ('C', 'D')]
结果是不是我们分析那样呢
分组总结
1.在网页中,想要什么内容就加()
2.先按整体正则匹配,然后提取分组()中的内容,
如果有两个及其以上分组()中的内容,则结果以元组形式显示
[(‘苹果’,‘红色’),(‘苹果’,‘绿色’),()]
以此类推
这里多说一点,就好像我们要抓取boss上的岗位和薪资,我们就可以把这两个元素加()
下面我们来一个实例,更好的去理解
我们爬取猫眼电影的T100榜
爬取流程和之前一样,这里就不多说,我们先查看这个页面
找到url规律
https://maoyan.com/board/4?offset=0
url为这个,查找规律,再点开第二页就行了
我们分析页面,我们要抓取的是电影名、主演、以及上映时间
我们检查一下源代码,看一下有没有相关数据
如图页面是有我们所需要的数据,我们可以多查看几个找到其中规律
我们需要关注的是包裹我们所要信息的div之类的如上图
我们所需要的正则大概是这样的
<div class="movie-item-info">.*?title="(.*?)".*?class="star">(.*?)</p>.*?releasetime">(*?)</p>
仔细研究以下这个正则,相信你会有很大收获,我们确定抓取位置
从
下面我们开始写程序抓取
from urllib import request, parse
import random
import time
from fake_useragent import UserAgent
import re
class MaoyanSpider(object):
def __init__(self):
self.url = 'https://maoyan.com/board/4?offset={}'
# 生成随机UserAgent
self.i = 0
def get_headers(self):
ua = UserAgent()
headers = {'User_Agent': ua.random}
return headers
# 获取响应内容
def get_page(self, url):
headers = self.get_headers()
print(url)
req = request.Request(url=url, headers=headers)
res = request.urlopen(req)
html = res.read().decode()
# 直接调用解析
self.parse_page(html)
# 提取数据
def parse_page(self, html):
re_bds = '<div class="movie-item-info">.*?title="(.*?)".*?class="star">(.*?)</p>.*?releasetime">(.*?)</p>'
pattern = re.compile(re_bds, re.S)
r_list = pattern.findall(html)
self.write_page(r_list)
# 保存
def write_page(self, r_list):
item = {}
for r in r_list:
# .strip用于去掉两端空白部分
item['name'] = r[0].strip()
item['star'] = r[1].strip()
item['time'] = r[2].strip()
print(item)
self.i += 1
# 入口函数
def run(self):
for offset in (0, 91, 10):
url = self.url.format(offset)
self.get_page(url)
# 随机休眠
# random.uniform随机生成浮点数
time.sleep(random.uniform(1, 2))
print('数量', self.i)
if __name__ == '__main__':
begin = time.time()
spider = MaoyanSpider()
spider.run()
stop = time.time()
print('执行时间:%2.f' % (stop - begin))
这里我们先把数据打印出来,储存到数据库后面再说,我们可以得到结果,成功抓取,总共有十页,猫眼管的有点严,抓了几次才成功,自己可以修改offset的范围来控制抓取页数。正则是这一章的重点,一点要多去理解,才能成功获取到你要的数据