沉淀,再出发:python爬虫的再次思考
一、前言
之前笔者就写过python爬虫的相关文档,不过当时因为知识所限,理解和掌握的东西都非常的少,并且使用更多的是python2.x的版本的功能,现在基本上都在向python3转移了,很多新的框架也不断的产生和使用,从一些新的视角,比如beautifulsoup,selenium,phantomjs等工具的使用,可以使得我们对网页的解析和模拟更加的成熟和方便。
二、python3爬虫
在网上有很多值得我们去爬取的资源,这些资源大体可以分为文档、图片、音频、视频、其他格式的资源这几类,我们用的最多的或许就是前面的两种了,特别是我们爬取文档,这是我们经常使用的一种资源,比如爬取一些影评,网络小说等等,另外我们还可以将文档存储到Excel的表格之中这样就更加的清晰了。
2.1、爬取糗事百科
这里面有很多的文档资源,我们可以通过page之后的页码将内容爬取出来,并且根据页面的格式进行解析和存储。
让我们查看元素,看一下需要爬取的内容对应的标签和格式:
理解了url的编码规律和页面元素的DOM关系,我们就可以写程序了:
import requests
from bs4 import BeautifulSoup def download_page(url):
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
r = requests.get(url, headers=headers)
return r.text def get_content(html, page):
output = """第{}页 作者:{} 性别:{} 年龄:{} 点赞:{} 评论:{}\n{}\n------------\n"""
soup = BeautifulSoup(html, 'html.parser')
con = soup.find(id='content-left')
con_list = con.find_all('div', class_="article")
for i in con_list:
author = i.find('h2').string # 获取作者名字
content = i.find('div', class_='content').find('span').get_text() # 获取内容
stats = i.find('div', class_='stats')
vote = stats.find('span', class_='stats-vote').find('i', class_='number').string
comment = stats.find('span', class_='stats-comments').find('i', class_='number').string
author_info = i.find('div', class_='articleGender') # 获取作者 年龄,性别
if author_info is not None: # 非匿名用户
class_list = author_info['class']
if "womenIcon" in class_list:
gender = '女'
elif "manIcon" in class_list:
gender = '男'
else:
gender = ''
age = author_info.string # 获取年龄
else: # 匿名用户
gender = ''
age = '' save_txt(output.format(page, author, gender, age, vote, comment, content)) def save_txt(*args):
for i in args:
with open('E:qiubai.txt', 'a', encoding='utf-8') as f:
f.write(i) def main():
# 我们点击下面链接,在页面下方可以看到共有13页,可以构造如下 url,
# 当然我们最好是用 Beautiful Soup找到页面底部有多少页。
for i in range(1, 14):
url = 'https://qiushibaike.com/text/page/{}'.format(i)
html = download_page(url)
get_content(html, i) if __name__ == '__main__':
main()
运行结果:
2.2、爬取文本并保存成Excel文件
在这里如果没有mysql数据库的话,我们已经将连接数据库并保存到数据库的功能给注释掉了。爬取拉钩网的网站信息,并且记录到Excel中。
import random
import time import requests
from openpyxl import Workbook
import pymysql.cursors def get_conn():
'''建立数据库连接'''
conn = pymysql.connect(host='localhost',
user='root',
password='root',
db='python',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
return conn def insert(conn, info):
'''数据写入数据库'''
with conn.cursor() as cursor:
sql = "INSERT INTO `python` (`shortname`, `fullname`, `industryfield`, `companySize`, `salary`, `city`, `education`) VALUES (%s, %s, %s, %s, %s, %s, %s)"
cursor.execute(sql, info)
conn.commit() def get_json(url, page, lang_name):
'''返回当前页面的信息列表'''
headers = {
'Host': 'www.lagou.com',
'Connection': 'keep-alive',
'Content-Length': '',
'Origin': 'https://www.lagou.com',
'X-Anit-Forge-Code': '',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'Accept': 'application/json, text/javascript, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest',
'X-Anit-Forge-Token': 'None',
'Referer': 'https://www.lagou.com/jobs/list_python?city=%E5%85%A8%E5%9B%BD&cl=false&fromSearch=true&labelWords=&suginput=',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'
}
data = {'first': 'false', 'pn': page, 'kd': lang_name}
json = requests.post(url, data, headers=headers).json()
list_con = json['content']['positionResult']['result']
info_list = []
for i in list_con:
info = []
info.append(i.get('companyShortName', '无'))
info.append(i.get('companyFullName', '无'))
info.append(i.get('industryField', '无'))
info.append(i.get('companySize', '无'))
info.append(i.get('salary', '无'))
info.append(i.get('city', '无'))
info.append(i.get('education', '无'))
info_list.append(info)
return info_list def main():
lang_name = 'python'
wb = Workbook() # 打开 excel 工作簿
# conn = get_conn() # 建立数据库连接 不存数据库 注释此行
for i in ['北京', '上海', '广州', '深圳', '杭州']: # 五个城市
page = 1
ws1 = wb.active
ws1.title = lang_name
url = 'https://www.lagou.com/jobs/positionAjax.json?city={}&needAddtionalResult=false'.format(i)
while page < 31: # 每个城市30页信息
info = get_json(url, page, lang_name)
page += 1
print(i, 'page', page)
time.sleep(random.randint(10, 20))
for row in info:
# insert(conn, tuple(row)) # 插入数据库,若不想存入 注释此行
ws1.append(row)
# conn.close() # 关闭数据库连接,不存数据库 注释此行
wb.save('E:{}职位信息.xlsx'.format(lang_name)) if __name__ == '__main__':
main()
运行结果:
2.3、爬取图片并保存
这里我们使用一个图片比较多的网站(http://meizitu.com)为例来展示:
import requests
import os
import time
import threading
from bs4 import BeautifulSoup def download_page(url):
'''
用于下载页面
'''
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
r = requests.get(url, headers=headers)
r.encoding = 'gb2312'
return r.text def get_pic_list(html):
'''
获取每个页面的套图列表,之后循环调用get_pic函数获取图片
'''
soup = BeautifulSoup(html, 'html.parser')
pic_list = soup.find_all('li', class_='wp-item')
for i in pic_list:
a_tag = i.find('h3', class_='tit').find('a')
link = a_tag.get('href')
text = a_tag.get_text()
get_pic(link, text) def get_pic(link, text):
'''
获取当前页面的图片,并保存
'''
html = download_page(link) # 下载界面
soup = BeautifulSoup(html, 'html.parser')
pic_list = soup.find('div', id="picture").find_all('img') # 找到界面所有图片
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:61.0) Gecko/20100101 Firefox/61.0"}
create_dir('pic/{}'.format(text))
for i in pic_list:
pic_link = i.get('src') # 拿到图片的具体 url
r = requests.get(pic_link, headers=headers) # 下载图片,之后保存到文件
with open('pic/{}/{}'.format(text, pic_link.split('/')[-1]), 'wb') as f:
f.write(r.content)
time.sleep(1) # 休息一下,不要给网站太大压力,避免被封 def create_dir(name):
if not os.path.exists(name):
os.makedirs(name) def execute(url):
page_html = download_page(url)
get_pic_list(page_html) def main():
create_dir('pic')
queue = [i for i in range(1, 72)] # 构造 url 链接 页码。
threads = []
while len(queue) > 0:
for thread in threads:
if not thread.is_alive():
threads.remove(thread)
while len(threads) < 5 and len(queue) > 0: # 最大线程数设置为 5
cur_page = queue.pop(0)
url = 'http://meizitu.com/a/more_{}.html'.format(cur_page)
thread = threading.Thread(target=execute, args=(url,))
thread.setDaemon(True)
thread.start()
print('{}正在下载{}页'.format(threading.current_thread().name, cur_page))
threads.append(thread) if __name__ == '__main__':
main()
2.4、爬取豆瓣书籍
from bs4 import BeautifulSoup
import requests
from openpyxl import Workbook
excel_name = "书籍.xlsx"
wb = Workbook()
ws1 = wb.active
ws1.title='书籍' def get_html(url):
header = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'}
html = requests.get(url, headers=header).content
return html def get_con(html):
soup = BeautifulSoup(html,'html.parser')
book_list = soup.find('div', attrs={'class': 'article'})
page = soup.find('div', attrs={'class': 'paginator'})
next_page = page.find('span', attrs={'class': 'next'}).find('a')
name = []
for i in book_list.find_all('table'):
book_name = i.find('div', attrs={'class': 'pl2'})
m = list(book_name.find('a').stripped_strings)
if len(m)>1:
x = m[0]+m[1]
else:
x = m[0]
#print(x)
name.append(x)
if next_page:
return name, next_page.get('href')
else:
return name, None def main():
url = 'https://book.douban.com/top250'
name_list=[]
while url:
html = get_html(url)
name, url = get_con(html)
name_list = name_list + name
for i in name_list:
location = 'A%s'%(name_list.index(i)+1)
print(i)
print(location)
ws1[location]=i
wb.save(filename=excel_name) if __name__ == '__main__':
main()
2.5、爬取豆瓣电影影评并保存到Excel中
#!/usr/bin/env python
# encoding=utf-8
import requests
import re
import codecs
from bs4 import BeautifulSoup
from openpyxl import Workbook
wb = Workbook()
dest_filename = '电影.xlsx'
ws1 = wb.active
ws1.title = "电影top250" DOWNLOAD_URL = 'http://movie.douban.com/top250/' def download_page(url):
"""获取url地址页面内容"""
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.80 Safari/537.36'
}
data = requests.get(url, headers=headers).content
return data def get_li(doc):
soup = BeautifulSoup(doc, 'html.parser')
ol = soup.find('ol', class_='grid_view')
name = [] # 名字
star_con = [] # 评价人数
score = [] # 评分
info_list = [] # 短评
for i in ol.find_all('li'):
detail = i.find('div', attrs={'class': 'hd'})
movie_name = detail.find(
'span', attrs={'class': 'title'}).get_text() # 电影名字
level_star = i.find(
'span', attrs={'class': 'rating_num'}).get_text() # 评分
star = i.find('div', attrs={'class': 'star'})
star_num = star.find(text=re.compile('评价')) # 评价 info = i.find('span', attrs={'class': 'inq'}) # 短评
if info: # 判断是否有短评
info_list.append(info.get_text())
else:
info_list.append('无')
score.append(level_star) name.append(movie_name)
star_con.append(star_num)
page = soup.find('span', attrs={'class': 'next'}).find('a') # 获取下一页
if page:
return name, star_con, score, info_list, DOWNLOAD_URL + page['href']
return name, star_con, score, info_list, None def main():
url = DOWNLOAD_URL
name = []
star_con = []
score = []
info = []
while url:
doc = download_page(url)
movie, star, level_num, info_list, url = get_li(doc)
name = name + movie
star_con = star_con + star
score = score + level_num
info = info + info_list
for (i, m, o, p) in zip(name, star_con, score, info):
col_A = 'A%s' % (name.index(i) + 1)
col_B = 'B%s' % (name.index(i) + 1)
col_C = 'C%s' % (name.index(i) + 1)
col_D = 'D%s' % (name.index(i) + 1)
ws1[col_A] = i
ws1[col_B] = m
ws1[col_C] = o
ws1[col_D] = p
wb.save(filename=dest_filename) if __name__ == '__main__':
main()
当然还可以爬取小说等其他资源。
三、使用selenium模拟真人操作
在编写程序之前,我们需要为python的运行环境之中加入selenium这个元素,因此我们使用pip来安装即可,pip是我们在安装python的时候一起安装的,当我们在环境变量中将python暴露出来的时候,pip也就出来了。
之后我们进入关于python的selenium官网,可以看到如何在不同的浏览器中配置和安装相应的驱动。
在这里我们下载了Firefox浏览器的驱动器之后将其放入python的安装目录中,这样既方便查找,又方便设为环境变量:
之后我们来模拟人工访问淘宝网站并抓取信息:
from selenium import webdriver
import time
class TB(object):
def __init__(self,keyword):
self.keyword = keyword
def start_taobao(self): driver =webdriver.Firefox() driver.get('http://www.taobao.com') search_input = driver.find_element_by_id('q') search_input.send_keys(self.keyword) search_btn = driver.find_element_by_class_name('btn-search') search_btn.click()
time.sleep(2) file_handle = open('E:%s.txt'%self.keyword,'w',encoding='utf-8')
for x in range(1,2):
print('获取第%s页的数据'%x) for x in range(1,11,2):
time.sleep(1) j = x/10 js = 'document.documentElement.scrollTop = document.documentElement.scrollHeight * %f'%j
driver.execute_script(js) shops = driver.find_elements_by_class_name('info-cont') if len(shops) ==0:
shops = driver.find_elements_by_class_name('J_MouserOnverReq') for shop in shops: file_handle.write(shop.text)
file_handle.write('\n') next_li = driver.find_element_by_class_name('next')
next_li.click() file_handle.close() driver.quit() print(__name__) if __name__ == '__main__':
keyword = input('输入关键词:')
tb =TB(keyword)
tb.start_taobao()
可以看到selenium的强大之处在于能按照我们的需要像人类一样的打开浏览器输入内容,点击按钮,抓取网页,最后关闭网页,非常的方便和强大。当然selenium也有很多的命令,获取网页,解析元素,点击按钮,执行js等,非常的有用,当然selenium还可以破解验证码,滑动条等机制。
四、phantomjs的使用
既然我们能够使用selenium模拟真人自动打开网页抓取响应信息,点击按钮,输入文字了,为什么还需要phantomjs呢?那是因为phantomjs是一个无头的浏览器,什么叫做‘无头’,其实就是没有图形界面的浏览器,基于webkit内核,只有命令行的界面,这样的话就能自然的通过设置规避一些图片以及音视频等,并且无需像我们之前使用selenium操作Firefox那样的打开浏览器了,这是非常方便的,因此业界一直都是使用selenium+phantomjs来配合抓取网页和分析网页的,可惜的是当Firefox和Chrome等浏览器也相应地推出了无头浏览器的版本之后,作为小厂的创意的phantomjs瞬间就没有立足之地了,市场就是这样的残酷,下面让我们看一下安装和使用吧:
在官网上下载相应版本:
下载之后我们解压并将phantomjs.exe暴露到环境变量之中,之后我们就可以在cmd中对phantomjs操作了。
首先让我们看一个简单的只使用phantomjs的例子:
var webPage = require('webpage');
var page = webPage.create(); page.open("http://www.baidu.com", function start(status) {
page.render('E:baidu.jpg');
phantom.exit();
});
我们可以看到在没有打开网页,也没有截图的情况下,我们使用phantomjs已经抓去了百度的页面并且对其截图放到了我们规定的目录下面,这是多么的神奇呀,如果再配合上selenium,那么这样的设计模拟真人并且不用再次打开浏览器的GUI就可以神不知鬼不觉的完成爬虫的工作了。
我们使用selenium和phantomjs来抓取网页并打印出来:
from selenium import webdriver
url = "https://www.taobao.com"
driver = webdriver.PhantomJS()
driver.get(url)
print (driver.page_source)
可以看到我们确实成功了,但是也出现了警告信息,意思是selenium使用phantomjs已经过时了,请使用Firefox或者Chrome的无头版本浏览器来代替,因此这个用js写的phantomjs的存在就变得举步维艰了。
可以看出selenium和phantomjs的出现其实更大成分是为了应付动态网页的相关技术,比如ajax,在不刷新网页的情况下,将数据通过json等方式传递回来,这样我们使用普通的爬虫是抓取不到的(除非找到相应的js文件再次请求一下,非常的耗时和复杂),但是使用selenium通过模拟真人操作是非常容易的,再加上无头浏览器phantomjs之后,提供了更强大的功能。因此一切技术的产生都是为了解决某种特定的问题,问题是永恒的,而解决问题的方法却是在不断地变化着和优化着,道高一尺魔高一丈,计算机科学就是在这样的压力之下不断地完善和发展的。
五、总结
Selenium是一个用于Web应用程序测试的工具。selenium的功能比较齐全,不只是获取动态网页, 比如数据的提取都可以完成, 但是不建议直接使用selenium来提取数据, 因为速度慢。 Selenium 测试直接运行在浏览器中,就像真正的用户在操作一样。 支持的浏览器包括IE,Mozilla和Firefox,还有*面的phantomjs浏览器等,我们可以通过selenium直接操作浏览器。但是启动firefox毕竟是图形化的界面, 肯定是会消耗大量内存和cpu ,phantomjs正是一款没有界面的浏览器但是还是同样能完成浏览器的渲染,这样如果我们的操作系统是没有界面的linux服务器上,phantomjs就大有用途了,并且phantomjs提供的设置项多, 比如可以设置忽略images的请求减少网络请求数。既然phantomjs只是一款浏览器, selenium就当然能像操作firefox一样很简单的操作phantomjs了。
PhantomJS是一个基于webkit的JavaScript API。它使用QtWebKit作为它核心浏览器的功能,使用webkit来编译解释执行JavaScript代码。任何可以在基于webkit浏览器做的事情,它都能做到。它不仅是个隐形的浏览器,提供了诸如CSS选择器、支持Web标准、DOM操作、JSON、HTML5、Canvas、SVG等,同时也提供了处理文件I/O的操作,从而使得可以向操作系统读写文件等。PhantomJS的用处可谓非常广泛,诸如网络监测、网页截屏、无需浏览器的 Web 测试、页面访问自动化等。
通过在python中加入多线程、beautifulsoup和selenium之后,我们可以看到使用python抓取网页是如此的便捷,并且还能保存成各种类型的文档,还能存储到数据库之中,这样的爬虫加以改进之后已经能够用于工业生产了。