关于工具集
1、 Python
3.6版本以上
建议使用virtualenv创建一个虚拟环境对当前项目进行一个隔离,
否则可能会出现比较痛苦的事情,pip与Python对不上。。。
如果使用pycharm这个IDE默认是可以使用virtualenv创建项目的。
(关于依赖环境的问题,这里多提一些,golang的go module在这方面非常先进,
通过一个go.mod文件定位各种依赖库及其相应版本,在Python中就得使用一些工具进行虚拟处理,实际就是一个隔离。
上次很惨痛的一个教训,就是用pip装完依赖库后import不了,磕了一天,最后也只是治标而没有治本)
2、Chromium浏览器
Chrome的开源版本,此次我们的web自动化控制的浏览器对象
3、asyncio
Python自3.4版本引入的标准库,是一个异步协程库,通过它我们实现多协程、异步操作。
(说实话,因为GIL的机制问题留下的阴影,到现在我也还不能确定这玩意是不是好使的异步,总觉得它又是假的。。。)
4、Pyppeteer
Python版本的Puppeteer,Puppeteer是Google基于Node.js开发的一个工具,主要是用来操纵Chrome浏览器的API。
简单来说,就是用Pyppeteer去控制浏览器,获取对应的浏览页。
5、bs4
解析网页HTML等的解析库,bs4功能强大,较为完全。
6、总结
总结一下上述工具集的关系,我们使用Python进行开发,使用pyppeteer控制Chromium浏览器模拟请求
获取网页内容,使用bs4以及正则表达式等对内容进行清洗和提炼得出关键性的数据,然后存入csv/json/数据库中去
具体步骤
1 、新建项目
# 命令行创建
python -m venv project # 创建项目环境
source project/bin/activate # 启动项目环境
deactivate # 退出项目环境
# Pycharm直接选择创建
2、查看网址robots.txt
虽然爬虫本身就是耍流氓,但是耍流氓之前我们要先看看规定,人家是否让我们耍流氓 (当然了,大部分是不让的,也可以偷偷耍... 这里就不得不提豆瓣了,写了那么多disallow,大部分入门教程还是拿它练手。。。 两年前它就是我的练手项目第一个) 举个例子,假设我们的url——www.httpbin.org/headers, 那么我们要先看看www.httpbin.org/headers/rob… 还好,此次项目的robots.txt如下
User-agent:*
Disallow:/zzbm/tjr/
这段话的意思是对于所有用户 /zzbm/tjr/ 下的所有东西不允许爬取,其它没说就是默认可以...
3、browser相关操作
辅助函数
from pyppeteer import launch
import tkinter
def screen_size():
"""使用tkinter获取屏幕大小"""
tk = tkinter.Tk()
width = tk.winfo_screenwidth()
height = tk.winfo_screenheight()
tk.quit()
return width, height
browser启动参数及配置
这里重点提一下executablePath,指定好对应目录,否则启动pyppeteer自己会去下载,比较慢
start_parm = {
# 启动chrome的路径
"executablePath": "/Applications/Chromium.app/Contents/MacOS/Chromium",
# 无头浏览器
"headless": False,
"args": [
'--disable-infobars', # 关闭自动化提示框
# '--window-size=1920,1080', # 窗口大小
'--no-sandbox', # 关闭沙盒模式
'--start-maximized', # 窗口最大化模式
],
}
# 创建浏览器对象
browser = await launch(**start_parm)
# 新建标签页
page = await browser.newPage()
width, height = screen_size()
# 设置窗口视图大小
await page.setViewport(viewport={"width": width, "height": height})
# 根据对应url获取页面内容
await page.goto(url)
page_text = await page.content()
# 进行解析操作
# 关闭浏览器
await browser.close()
4、分析HTML结构,提取关键信息
刚才获取的page_text我们可以先存储起来,通过文件查看并分析
soup = BeautifulSoup(page_text, "lxml")
# print(soup.prettify())
with open(f"html/gaokao_{count}.html", 'w') as f:
# print(type(soup.prettify()))
f.write(soup.prettify())
在gaokao.html文件中找到关键性结构
<tr>
<td class="js-yxk-yxmc">
<a href="/sch/schoolInfo--schId-1.dhtml" target="_blank">
北京大学
</a>
</td>
<td>
北京
</td>
<td>
教育部
</td>
<td>
综合
</td>
<td>
本科
</td>
<td class="ch-table-center">
<i class="iconfont ch-table-tick">
</i>
</td>
<td class="ch-table-center">
</td>
<td class="ch-table-center">
<i class="iconfont ch-table-tick">
</i>
</td>
<!--todo 院校满意度-->
<td class="ch-table-center ch-table-link">
<a class="js-alert-myd" data-id="99617187" href="###">
4.7
</a>
</td>
</tr>
接下来我们就可以根据它的标签、class、id等用bs4进行定位(这一步需要一些前端基础)
from bs4 import BeautifulSoup
content = soup.select("html body div div div tr td")
# 初始化表头
headers = ["院校名称", "院校所在地", "教育行政主管", "院校类型", "学历层次", "一流大学建设高校", "一流学科建设高校", "研究生院", "满意度"]
write_to_csv(count, headers)
提取具体内容
tmp_result = []
result = []
for index in range(len(content)):
# print(type(c))
# print(c.name, c.attrs, len(c.contents), type(c.contents))
# print("c.contents: ", c.contents)
# print("========================")
c = content[index]
# 中文匹配模式
pattern = re.compile(r'[\u4e00-\u9fa5]+')
# 评分匹配模式
pattern_num = re.compile(r'\d\.\d')
p = re.compile(r'<i class="iconfont ch-table-tick"></i>')
flag = False
for i in c.contents:
# print(type(i))
# print(i)
res = re.search(pattern, str(i))
if res != None:
# print(type(res.group()))
tmp_result.append(str(res.group()))
# print(res.group())
break
else:
num = re.search(pattern_num, str(i))
if num != None:
tmp_result.append(str(num.group()))
# print(num.group())
break
else:
rep = re.search(p, str(i))
if rep != None:
flag = True
break
总结——合并成bs4_parse函数
def bs4_parse(count, page_text):
"""使用bs4对文本进行解析,以及存储html源文件和写入csv文件"""
soup = BeautifulSoup(page_text, "lxml")
# print(soup.prettify())
with open(f"html/gaokao_{count}.html", 'w') as f:
# print(type(soup.prettify()))
f.write(soup.prettify())
content = soup.select("html body div div div tr td")
# print(content)
# print(type(content), len(content))
# 初始化表头
headers = ["院校名称", "院校所在地", "教育行政主管", "院校类型", "学历层次", "一流大学建设高校", "一流学科建设高校", "研究生院", "满意度"]
write_to_csv(count, headers)
tmp_result = []
result = []
for index in range(len(content)):
# print(type(c))
# print(c.name, c.attrs, len(c.contents), type(c.contents))
# print("c.contents: ", c.contents)
# print("========================")
c = content[index]
# 中文匹配模式
pattern = re.compile(r'[\u4e00-\u9fa5]+')
# 评分匹配模式
pattern_num = re.compile(r'\d\.\d')
p = re.compile(r'<i class="iconfont ch-table-tick"></i>')
flag = False
for i in c.contents:
# print(type(i))
# print(i)
res = re.search(pattern, str(i))
if res != None:
# print(type(res.group()))
tmp_result.append(str(res.group()))
# print(res.group())
break
else:
num = re.search(pattern_num, str(i))
if num != None:
tmp_result.append(str(num.group()))
# print(num.group())
break
else:
rep = re.search(p, str(i))
if rep != None:
flag = True
break
if res == None and num == None:
tmp_result.append(str(flag))
# print("flag: ", flag)
if (index+1) % 9 == 0:
print(tmp_result)
tmp_result = list(map(lambda x: [x], tmp_result))
#write_to_csv(count, tmp_result)
result.append(tmp_result)
tmp_result = []
write_to_csv(count, result, 2)
辅助函数——写入csv文件
def write_to_csv(count, lst, flag = 1):
path = f'data/yuanxiaoku_{count}.csv'
with open(path, 'a+', newline='') as f:
csv_write = csv.writer(f)
if flag == 1:
csv_write.writerow(lst)
elif flag == 2:
csv_write.writerows(lst)
5、根据url特征,进行异步操作
此次url特征是20条内容为一页,也就是参数以20递增,总共141页,使用异步协程完成
if __name__ == '__main__':
tasks = []
for i in range(0, 2800, 20):
url = f'https://xxx{i}.dhtml'
coroutine = main(url, str(i))
tasks.append(coroutine)
asyncio.get_event_loop().run_until_complete(asyncio.gather(*tasks))
总结
这个项目因为网址的反爬手段措施基本没有,所以其实用requests进行一个get请求就能完成。
我这个仅仅是想锻炼一下pyppeteer以及asyncio的使用,已经大半年没用Python,略有些生疏,这里做一个个小小的记录。
如果涉及到天猫淘宝那之类种验证码、滑块登录会相对更复杂一些,甚至还要会写一点js脚本,更夸张有时候还有抠像素点
爬虫方面的工具集更新还是比较快的,去年这个时候还在做Python讲师,完了吭哧吭哧用phantomjs写了个小玩意
一顿撸完发现因为大牛之间的矛盾,phantomjs已经不更新。。。
希望哪天也能这么秀,说不干一群人人用的轮子就抛锚了
补充:这个项目还没有完全对需求得更改进行适应,后续完成再放出git地址。
闲话
应黄老板的需求,共同完成一个项目,我一个后台工程师去写前端。
数据库没数据叫我写爬虫去抓,今儿还问我能不能改PHP代码...这很不对头。
话说golang和C++我还没写完全写明白呢...
技术栈深度比广度更重要,有更多的时间可以多去看看各种开源组件的源码比如redis之类的,大牛的源码有很多精彩。
(我老师看到这应该想捶我了,道理都知道,就是爱瞎玩。。。)
全栈工程师本身就是个伪概念,无论哪方面的开发尽量往一个方向深挖,其它的看看猪跑就好,不要所有猪肉都去吃。
Keep It Simple, Stupid!