市面上python爬虫的书籍、博客很多,但把整个架构讲清楚的往往失于笨重,而针对某一项讲解的话又很难把整个架构讲清楚,容易导致新人看半天不知道咋整。窃以为真的要收集需要的资料需要的爬虫技术其实不多,我希望能在接下来的篇章中把整个数据采集项目的方法完整的呈现给大家。为了节省篇幅,很多地方一些一百度就能出来的细节用法都未直接给出,所以请结合百度食用本文
一、下载网页
1.确认网页模式
如果能在网页源代码内找到你需要的一切内容(在源代码界面ctrl+f搜索),这个网页就是静态网页。如果不能,看你需要的内容是一打开网页就存在还是需要点击获取。如果是打开网页就存在,只需要简单应用selenium等待页面加载完成即可,如果需要点击加载或者点击跳转,那就需要模拟交互,需要较为复杂的selenium模拟点击操作。
如果网页为静态链接,请略过以下所有内容,直接看第二部分--内容搜索。如果网页是动态链接,请按照以下步骤操作:
1)如果只需要一个网页或者少量网页,ctrl+a全选,右键单击-查看选中部分源代码,ctrl+c复制,粘贴到txt中进行用soup后续处理查找即可
2)如果需要遍历多个网页,下载fiddler查找是否存在接口。这是一个简单易用的软件,教程很多,通过这个软件可以轻松找到网站给出接口,如果这个网站存在接口。这样就可以绕开费时的动态链接解析。(PS:先固定一个页面,刷新,新出来的内容里灰色的都是不可用的,剩下几个挨个试试就能找到接口了)
3)如果网站不存在接口或者接口中不存在你需要的内容,那老老实实selenium+phantomjs解析动态链接和模拟交互吧。用法可以参照http://cuiqingcai.com/2599.html。也可以采用pyspider等框架,参考http://cuiqingcai.com/2652.html
2.获取网页源代码
判断完之后,根据你所处的情况采取相应操作。静态网页获取源代码的程序如下,在此我采用了requests库获取,因为requests库的功能非常齐全(关键是用他打字少),可以加各种参数,挂载代理,解析json格式(重要!接口给出一般是json格式文件)等功能,能充分满足我们的需求。具体了解用法可以百度或者import之后dir(requests)查看。输出我选择的是soup,因为BeautifulSoup能解决一切能获取完整源代码的问题,而且非常轻松,在第二部分我将会重点分析findAll和select两种方法。这两个库都需要安装,pip安装即可,对安装包不清楚的可以参考http://blog.csdn.net/qq_38284543/article/details/72964440。引用的时候import requests,from bs4 import
BeautifulSoup即可。
def download(_id,num_retries=1500,headers=headers):
url=basicurl+str(_id)#因为我们处理的问题经常只有ID能唯一识别,收集的信息都通过ID唯一识别,所以此处采用拼接url,建议熟练掌握格式化。比较正规的方式是%d,%s或者format{}
headers={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:54.0) Gecko/20100101 Firefox/"#headers获取方法参考http://www.cnblogs.com/Maple2cat/p/Python.html
` }
try:
html=requests.get(url).content#取content是直接把访问页面源代码处理成字符串。如果请求访问失败或者页面不存在就会报错,但因为网页总有访问异常的时候,一定要防止异常,所以干脆都通过try解决。except后面不加异常类型是因为这个地方如果出现异常,它的类型一般是申请连接失败
except:
if num_retries>0:
print num_retries#这个地方可以把错误的url也打印出来,形如print url。也可以把访问失败的url存入列表方便回头重爬
return download(_id,num_retries-1)#迭代实现断线重连
soup=bs(html,'lxml')#因为soup基本上能轻松解决一切能抓到源代码的问题,所以我统一输出soup,后文请勿对soup进行更改
return soup
二、筛选内容
一般需要筛选的内容包括单值索引和列表索引。假设你想寻找‘名字’属性,往往页面返回给你的是单一返回值(用逗号隔开也算单一返回值),也就是说在源代码中,你需要寻找的地方只有一处。假如你想寻找‘反应对象’属性,往往页面返回给你的是一个列表(一般存在于表格中),也就是说你需要在源代码中寻找你希望的返回值的统一格式以及把它们和其他不需要的值区分开。 另外一种区分的方式是筛选的对象。有时候我们通过各种手段获取完整的源代码,有时候我们直接从网页接口获取数据,这两种情况下筛选数据的方法是不一样的。下面将分别描述具体操作。
1.从直接获取的源代码中筛选
从直接获取的源代码中筛选需要的信息一般用soup.select结合soup.findAll(或者find_all,不能写成findall)。其中findAll只用于最外层的辅助查找或者单值筛选,列表的精确筛选靠select。
1)单值筛选
单值筛选是非常简单的,因为页面中往往你索引的关键词和它对应的值会靠在一起--处于同一个标签中,而且往往是tr标签(万一不是tr标签请自行修改,火狐浏览器的元素审查能够超简单的帮你找到标签类型)。为此,只需要适当修改以下这段代码就能轻松筛选出希望的结果。def unique(soup,name,a='th',b='td'):#传入的soup的下载的源代码处理后的对象,name是你需要的值的属性/名字,请保持和网页中大小写一致,以字符串格式传入。已有的name,a,b默认值会被你传入的值覆盖。a和b分别是定义你需要的属性和值所在的父标签中的对应子标签
new=''
for i in soup.findAll('tr'):
try:
if i.find(a).text.strip()==name:#strip去除空格和换行符,防止页面加空格捣乱
new=i.find(b).text.strip()
except:
pass
return new
示例讲解:print unique(soup,'PubChem CID','th','td')。这表示我希望寻找的属性是PubChem CID,网页中标记PubChem CID属性的标签为‘th’,标记PubChem CID对应值的标签为‘td’。同样的,你可以通过火狐浏览器的查看元素功能,轻松的写出筛选你希望的值的代码。就我经验来看,所有的单值筛选都可以通过这样的方法获得,如果一时间找不到,试试火狐的查看元素,把所附近的标签都展开(把垂直的△点成倒立的)。万一还是找不到,试试通过soup.findAll('')[i]的方法来获取下一位标签。
2)列表筛选
列表筛选是一件较为复杂的活儿,你需要找出你希望得到的属性的值在源代码中对应的标签独一无二的地方。通常我们处理的页面都是少量表格,如果有多个表格一般是你找的界面不对,有更合适的对应界面。因此表格中的内容我们可以通过在源代码中ctrl+f搜索<table来定位。不加右括号是因为table标签往往后面会跟一些其他属性。往往通过table标签定位采用的是形如soup.find('table').find('tbody').findAll('style':'nowrap')的代码。 能通过table标签定位是最好的情况。很多时候需要css选择器select通过class或者id直接定位。在select方法中,class被简写为.,id被简写为#,每一层标签之间用空格隔开或者用其他符号连接,可以根据标签是否存在或者标签值来查找。具体的使用方法可以参考soup官方文档中的解释https://www.crummy.com/software/BeautifulSoup/bs3/documentation.zh.html#Searching by CSS class
示例讲解:print soup.select("#drugInfoTable tr[class*=bg] a")。这里通过id定位了表格里数据所在位置,然后通过tr,class和a标签精准定位了数据位置(用*代表1或2的方法可以参考正则表达式)。当然,很多时候我们需要定位的不是属性对应的值的名字,而是它对应的跳转链接,或者说切片之后得到的ID。这时候对取出的列表中的元素取一个.href即可。这个操作代表取这个标签中的href属性对应的值,也就是我们希望得到的网址。同样的,其他的列表(一般是表格中的值)也能通过同样的方法获取。欢迎大家探索select方法,这是一个极度简单且好用的方法。
再给出一个偷懒的封装函数。警告!!!请勿在任何其他人能接触的程序中使用eval函数!!!
def getlist(soup,a,b):
new=''
try:
for i in eval(a):#a是定位筛选内容所在位置的代码,需要高度凝练,建议熟练掌握for in循环和切片
try:#双重捕获异常是因为两个地方都可能出现异常,单单捕获一次会遗漏信息
new='|'.join(eval(b))#b是取出筛选内容的代码,此处可以加入if判断。为了方便存入数据库,输出结果以'|'连接成一个大的字符串,如果希望用列表存储就换成append,new相应的从''换成[]
except:#此处最好加入可能出现的异常类型,可以先不捕获异常跑一次看看可能出现哪些异常
pass
except:
pass
return new
2.从接口获取的json文件中筛选
接口中给出的一般是json格式的文件,json是一个列表和字典相互嵌套的大字典(不排除有元组)。为了从json中熟练筛选数据,请务必充分理解字典的各种用法(百度很好找)。本文仅介绍一个遍历json文件处理成的字典寻找需要属性对应值的函数(此方法由w表哥倾情提供)。
def findkeyvalue(keyname, tar):
if keyname in tar and isinstance(tar, dict):
return tar[keyname]
if isinstance(tar, dict):
tar = tar.values()
if isinstance(tar, list):
for i in tar:
if isinstance(i, dict) or isinstance(i, list):
value = findkeyvalue(keyname, i)
if value is not None:
return value
列表一般只能逐层对应过去,如果有好的遍历方法,欢迎留言,谢谢支持!
三、数据存取
由于数据库没有统一标准,所以在这里就不介绍数据库,略微介绍一下其他常用数据存取方式
1.txt文件读写
file= open('aim.txt').read()#读取一个文件到字符串中,注意加绝对或者相对路径。也可以按行读取或者按其他方法读取,自行百度
list=eval(file)#从字符串中取出其中的列表或字典或元组
file=open('aim.txt','w')#以写模式打开一个文件
file.write(str(list))#写入的必须是str格式,可以通过强制类型转换写入列表
file.close()#一定要记得断开程序和文件的连接
2.csv文件读写
import csv
with open("aim.csv", "r") as csvfile:
reader=csv.reader(csvfile) #读取csv文件,返回的是迭代类型
for i in reader:
print i#或者用append存入列表也可以
csvFile.close()
csvFile=open('aim.csv','w', newline='') #设置newline,否则两行之间会空一行
writer = csv.writer(csvFile)
for i in range(len(list)):
writer.writerow(list[i])
csvFile.close()
3.excel文件读写
import xlrd import xlwt
data=xlrd.open_workbook('aim.xls')#有时候是xlsx
table=data.sheets()[0]#表示是表格中的第一张工作表,如果需要换页就改0
list=table.col_values(0)#表示把表的第一列赋值给list,换成其他列以此类推,行就用row
newTable='aim.xls'
wb=xlwt.Workbook(encoding='utf-8')
ws=wb.add_sheet('test')
count=0
for i in list:
ws.write(count,a,i)#为了用count标记行号
count+=1
wb.save('aim.xls')
注:如果有多列,可以封装插入函数循环。另外xlwt有数据规模限制,最多插入256列*32768行。如果需要往excel中插入更多数据,请百度openpyxl(记得转换列号)
4.dict格式文件读写
import pickle
with open('aim.txt','rb') as file:
dict=pickle.load(file)
with open('aim.txt','wb') as file:
pickle.dump(dict,file)
file.close()
注:json也有类似操作,通过loads和dumps方法
四、结语
爬虫与反爬虫的战斗一直持续着,在社交网站和爬虫书籍中各种进攻和抵抗方法层出不穷。但对我来说,爬虫只是学习过程中中微不足道的一个小站,能用最少的时间精力知识来获取到需要的数据,就不想花太多时间深入了解,毕竟,还有更多,更美好的风景在远方等着我。希望本文能让你了解一整套数据采集的流程和必备的知识储备,最终成为一个合格的scraper