-
前言
初次接触Python,是以为测试同事用来做自动化测试,这两天有空“研究”了一下Python网络爬虫,所谓“研究”,其实就是了解,并跟着慕课网上的教学视频,写了一个爬取百度百科的定向爬虫。Demo传送门
注意:小生是刚接触Python,这里只是粗略记录下我的学习,所以深度优先,文中如有错误的地方请在留言处批评指正,小生感激不尽。
-
爬虫-百度百科
简单说,爬虫可以理解为网页蜘蛛,指通过我们特定的规则,自动化的从互联网获取我们需要的数据的一种技术手段。百度百科中有些简单的介绍,感兴趣的同学可以预先浏览学习下。
-
原理
简单的网络爬虫,就是从一个网页的URL开始,下载该URL对应的网页,通过正则或其他技术手段从下载的内容中获取想要的数据进行存储或输出,并提取该URL中包含的其他URL,放入URL管理队列中,重复执行上述操作。
-
爬虫组成-结构
一般来说,爬虫程序的简单组成基本上可分为:URL管理器、数据下载器、数据解析器、应用程序等几部分。
- URL管理器:管理URL,包括已经爬取过数据的URL和未爬取即将爬取的URL。
- 数据下载器:通过一个URL下载网页,将网页转换成一个字符串,网页下载器有urllib2(Python官方基础模块)包括需要登录、代理、和cookie,requests(第三方包)。
- 数据解析器:将数据下载器下来的数据,按照我们的规则,进行数据提取。也可以根据DOM树的解析方式来解析。网页解析器有正则表达式(直观,将网页转成字符串通过模糊匹配的方式来提取有价值的信息,当文档比较复杂的时候,该方法提取数据的时候就会非常的困难)、html.parser(Python自带的)、beautifulsoup(第三方插件,可以使用Python自带的html.parser进行解析,也可以使用lxml进行解析,相对于其他几种来说要强大一些)、lxml(第三方插件,可以解析 xml 和 HTML),html.parser 和 beautifulsoup 以及 lxml 都是以 DOM 树的方式进行解析的。
-
应用程序:将数据解析器中提取的数据进行存储或输出。
援引网络上一张爬虫工作流程的图片.png
-
简单实践
大致了解了下爬虫的组成,我们就可以进行实践了,这里我们用的是Python语言,项目也很简单,只有几个文件,具体目录如下
目录.png
-
URL管理器(url_manager.py):
-
class UrlManager(object):
- init(self):初始化操作
def __init__(self): # 使用set存储URL,防止重复 self.new_urls = set() self.old_urls = set()
- add_new_url(self, url):添加单个URL
# 添加单个URL def add_new_url(self, url): # 检查是否为空 if url is None: return # 检查是否已经添加到待爬取和未爬取集合中,若在return if url in self.new_urls and url in self.old_urls: return else: self.new_urls.add(url)
- add_new_urls(self, urls):添加多个url
# 添加多个url def add_new_urls(self, urls): if urls is None: return for url in urls: self.add_new_url(url)
- has_new_url(self):是否还有未爬取数据的URL
# 是否还有未爬取数据的URL def has_new_url(self): return len(self.new_urls) != 0
- get_new_url(self):获取一个待爬取数据的URL
# 获取一个待爬取数据的URL def get_new_url(self): # 在未爬去数据的URL中获取将要爬去数据的URL,并在new_urls中删除 new_url = self.new_urls.pop() # 将new_url添加到已经使用的URL中 self.old_urls.add(new_url) # 返回将要爬去数据的URL return new_url
-
-
数据下载器(html_download.py):
-
class HtmlDownloader(object):
- 设置https自动校验
# 如果访问的是https,会自动校验,此句停止自动校验 ssl._create_default_https_context = ssl._create_unverified_context
- download(self):下载网页
@staticmethod def download(url): # 下载URL中的数据 if url is None: return with request.urlopen(url) as f: if f.getcode() != 200: print('请求失败') return None return f.read().decode('utf-8')
-
-
数据解析器(html_parser.py):
-
class HtmlParser(object):
- _get_new_urls(soup, page_url):从结果中提取符合规则的待爬取URL
@staticmethod def _get_new_urls(soup, page_url): # /item/Python/407313,网页URL相对路径 # new_urls 用于存放提取出来的新URL的集合,集合内不会存在相同元素 new_urls = set() # links 是通过BeatifulSoup find_all按照一定的规则获取到的所有link,find_all方法查询所有符合条件的 links = soup.find_all('a', href=re.compile(r'/item/\w+/\d+')) for link in links: new_url = link['href'] # 拼接全路径 new_full_url = urljoin(page_url, new_url) # 向集合中添加拼接后的全路径 new_urls.add(new_full_url) print('page_url: %s, link: %s, new_url: %s, full_url: %s' % (page_url, link, new_url, new_full_url)) return new_urls
- _get_new_data(soup, page_url):从结果中提取符合规则的数据
@staticmethod def _get_new_data(soup, page_url): res_data = {'url': page_url} # <dd class="lemmaWgt-lemmaTitle-title"><h1>Python</h1> # title_node = <h1>Python</h1> # 获取标题节点 title_node = soup.find('dd', class_='lemmaWgt-lemmaTitle-title').find('h1') res_data['title'] = title_node.get_text() # <div class="lemma-summary" label-module="lemmaSummary"> # summary 获取简介节点 summary_node = soup.find('div', class_='lemma-summary') res_data['summary'] = summary_node.get_text() return res_data
- parse(self, page_url, html_cont)
def parse(self, page_url, html_cont): if page_url is None or html_cont is None: return soup = BeautifulSoup(html_cont, 'html.parser') # 提取网页中的其他URL new_urls = self._get_new_urls(soup, page_url) new_data = self._get_new_data(soup, page_url) return new_urls, new_data
-
-
输出/应用程序(html_output.py):
-
class HtmlOutputer(object):
- init(self):
def __init__(self): #初始化数组 self.data = []
- collect_data(self, data):将每一条解析出来的结果,存放到数组中,以便在HTML页面中输出
# 将每一条解析出来的结果,存放到数组中,以便在HTML页面中输出 def collect_data(self, data): self.data.append(data)
- output_html(self):将结果遍历输出到网页resource.html中
def output_html(self): with open('resource.html', 'w') as file: file.write('<html>') file.write('<head>') file.write('<meta charset = "utf-8">') file.write('<title>') file.write('爬取结果') file.write('</title>') file.write('</head>') file.write('<body>') file.write('<table border = "1" cellspacing = "0">') file.write('<tr><td>标题</td><td>链接</td><td>描述简介</td></tr>') for data in self.data: print(data) string = "<tr><td>%s</td><td><a href=%s>%s</a></td><td>%s</td></tr>" % (data['title'], data['url'], data['url'], data['summary']) file.write(string) file.write('</table>') file.write('</body>') file.write('</html>') file.close()
-
-
启动程序入口(spider_main.py)
- class SpiderMain(object):
- init(self):
def __init__(self): self.urls = url_mananger.UrlManager() self.downloader = html_download.HtmlDownloader() self.parser = html_parser.HtmlParser() self.outputer = html_output.HtmlOutputer()
- craw(self, root_url):
def craw(self, root_url): if root_url is None: return count = 1 # 向URL管理器中添加root_url self.urls.add_new_url(root_url) while self.urls.has_new_url(): try: # 获取要爬去数据的URL new_url = self.urls.get_new_url() # 下载URL对应的html html_cont = self.downloader.download(new_url) # 解析下载的数据 new_urls, new_data = self.parser.parse(new_url, html_cont) # 将解析出来的相关urls添加到urls.new_urls中 self.urls.add_new_urls(new_urls) # 将解析出来的数据保存到outputer中 self.outputer.collect_data(new_data) # 只爬取100条数据 if count == 100: break count = count + 1 except ValueError: print('aaa,failed') # 输出一个html页面 self.outputer.output_html()
- main(): 启动初始化
def main(): # 设置入口URL root_url = 'https://baike.baidu.com/item/Python/407313' # 实例化一个爬虫对象 obj_spider = SpiderMain() obj_spider.craw(root_url)
- 启动程序
if __name__ == '__main__': # 启动 main()
- class SpiderMain(object):
-
Q&A
Q1: https网络请求可能会验证不通过
A1:在html_download.py
中import ssl
,并加入如下代码ssl._create_default_https_context = ssl._create_unverified_context
Q2:
urllib2
无法导入
A2:可以使用from urllib import request
,貌似3.x版本已经废弃了urllib2
Q3:
urlpath
无法导入
A3:可以使用from urllib.parse import urljoin
Q4: 获取不到数据
A4: 可能是百度百科换了URL格式,或者换了网页中元素标签,可查看网页源码,进行微调。
网友评论