赞
踩
这边有一个用来测试的网站点击跳转
requests
是Pytho
n的一个第三方HTTP
(Hypertext Transfer Protocol
,超文本传输协议)库,它比Python
自带的网络库urllib
更加简单、方便和人性化。使用requests
可以让Python
实现访问网页并获取源代码的功能。
使用requests获取网页的源代码,最简单的情况下只需要两行代码:
#使用requests获取源代码
import requests
source = requests.get('https://www.baidu.com').content.deocde()
使用浏览器来访问网页,看起来只需要输入网址就可以。但其实网页有很多种打开方式,最常见的是GET
方式和POST
方式。
在浏览器里面可以直接通过输入网址访问的页面,就是使用了GET
方式。
还有一些页面,只能通过从另一个页面单击某个链接或者某个按钮以后跳过来,不能直接通过在浏览器输入网址访问,这种网页就是使用了POST
方式。
GET方式
对于使用GET
方式的网页,再Python
中可以使用requests
的get
方法获取网页的原码
url = 'https://www.baidu.com'
html = requests.get(url)
html_text = html.text()
html_bytes = html.content
html_str = html_bytes.decode()
html是全部的网页数据
content是将数据转换成二进制数据,一般用于读取图片数据
text是将数据转换成网页自己编码的字符串,一般用于读取文字数据
decode将数据从二进制转换成字符串
POST方式
网页的访问方式除了GET
方式以外,还有POST
方式。有一些网页,使用GET
和POST
方式访问同样的网址,得到的结果是不一样的。还有另外一些网页,只能使用POST
方式访问,如果使用GET
方式访问,网站会直接返回错误信息。
import requests
url = 'http://exercise.kingname.info/exercise_requests_post'
data = {
'name': 'value1',
'password': 'value2'
}
html = requests.post(url, data=data).content.decode()
其中,data
这个字典的内容和项数需要根据实际情况修改,Key
和Value
在不同的网站是不一样的。而做爬虫,构造这个字典是任务之一。
还有一些网址,提交的内容需要是JSON
格式的,因此post()
方法的参数需要进行一些修改:
import requests
url = 'http://exercise.kingname.info/exercise_requests_post'
data = {
'name': 'value1',
'password': 'value2'
}
json = {
'name': 'value1',
'password': 'value2'
}
html_data = requests.post(url, data=data).content.decode()
html_json = requests.post(url, json=data).content.decode()
在上面使用requests
我们可以拿到下面的数据
那我们该如何拿到标题和正文?
import requests
import re
url = 'http://exercise.kingname.info/exercise_requests_get.html'
page = requests.get(url).content.decode()
title = re.search('title>(.*?)<', page, re.S)[1]
print(title)
content = re.findall('p>(.*?)<', page, re.S)
# 将文本拼接再一起
content_str = ''.join(content)
print(content_str)
search和finall的区别就是:前者返回第一个符合条件的数据,后者返回的是一个数组
跳转顶部
虽然我们初步掌握了requests与正则表达式以后,可以爬取一些简单的网址,但是此时的爬虫只有一个进程、一个线程,因此称为单线程爬虫。单线程爬虫每次只访问一个页面,不能充分利用计算机的网络带宽。一个页面最多也就几百KB,所以爬虫在爬取一个页面的时候,多出来的网速和从发起请求到得到源代码中间的时间都被浪费了。
如果可以让爬虫同时访问10个页面,就相当于爬取速度提高了10倍。为了达到这个目的,就需要使用多线程技术了。
微观上的单线程,在宏观上就像同时在做几件事。这种机制在I / O
(Input / Output
,输入 / 输出)密集型的操作上影响不大,但是在CPU
计算密集型的操作上面,由于只能使用CPU
的一个核,就会对性能产生非常大的影响。所以涉及计算密集型的程序,就需要使用多进程,Python
的多进程不受GIL
的影响。
爬虫属于I / O
密集型的程序,所以使用多线程可以大大提高爬取效率。
multiprocessing
本身是Python
的多进程库,用来处理与多进程相关的操作。但是由于进程与进程之间不能直接共享内存和堆栈资源,而且启动新的进程开销也比线程大得多,因此使用多线程来爬取比使用多进程有更多的优势。multiprocessing
下面有一个dummy
模块,它可以让Python
的线程使用multiprocessing
的各种方法。
dummy
下面有一个Pool
类,它用来实现线程池。这个线程池有一个map()
方法,可以让线程池里面的所有线程都“同时”执行一个函数。
例如,计算0到10000每个数的平方
startTime = datetime.datetime.now()
for num in range(10000):
print(num * num)
endTime = datetime.datetime.now()
print(f'执行代码的时间是{endTime - startTime}')
使用多线程的方式
startTime = datetime.datetime.now()
def calc_power2(num):
return num * num
pool = Pool(3)
origin_num = [x for x in range(10000)]
result = pool.map(calc_power2, origin_num)
print(f'计算0-9的平方分别为:{result}')
endTime = datetime.datetime.now()
print(f'执行代码的时间是{endTime - startTime}')
在上面的代码中,先定义了一个函数用来计算平方,然后初始化了一个有3个线程的线程池。这3个线程负责计算10000个数字的平方,谁先计算完手上的这个数,谁就先取下一个数继续计算,直到把所有的数字都计算完成为止。
在这个例子中,线程池的map()
方法接收两个参数,第1个参数是函数名,第2个参数是一个列表。注意:第1个参数仅仅是函数的名字,是不能带括号的。第2个参数是一个可迭代的对象,这个可迭代对象里面的每一个元素都会被函数clac_power2()
接收来作为参数。除了列表以外,元组、集合或者字典都可以作为map()
的第2个参数。
由于爬虫是I / O
密集型的操作,特别是在请求网页源代码的时候,如果使用单线程来开发,会浪费大量的时间来等待网页返回,所以把多线程技术应用到爬虫中,可以大大提高爬虫的运行效率。
系啊面写一段代码来比较
import requests import time from multiprocessing.dummy import Pool def query(url): requests.get(url) start = time.time() for i in range(100): query('https://baidu.com') end = time.time() print(f'单线程循环访问100次百度首页,耗时:{end - start}') start = time.time() url_list = [] for i in range(100): url_list.append('https://baidu.com') pool = Pool(5) pool.map(query, url_list) end = time.time() print(f'5线程访问100次百度首页,耗时:{end - start}')
发现使用多线程比单线程的速度快乐很多,但是是不是线程越多越好呢?并不是,因为我们可以发现5个线程运行的时间其实是比单个线程运行的五分之一是要多的,也就是线程也是需要消耗资源的。这也从侧面反映了Python
的多线程再微观上还是串行的,所以如果线程池设置的过大线程的切换而导致的开销可能会抵消多线程带来的性能提升
跳转顶部
在角色扮演类游戏中,玩家需要在游戏里领取任务。有的人喜欢一次只领取一个任务,把这个任务做完,再去领下一个任务,这就叫作深度优先搜索。
还有一些人喜欢先把能够领取的所有任务一次性领取完,然后去慢慢完成,最后再一次性把任务奖励都领取了,这就叫作广度优先搜索。
假设下图是某在线教育网站的课程分类,需要爬取上面的课程信息。从首页开始,课程有几个大的分类,比如根据语言分为Python
、Node.js
和Golang
。每个大分类下面又有很多的课程,比如Python
下面有爬虫、Django
和机器学习。每个课程又分为很多的课时。
在深度搜素优先的情况下,爬取的路线如图所示(序号从小到大)
也就是爬虫会先将一个课程全部爬取完成后,才继续爬取下一个内容
在广度优先搜索的情况下,爬取路线的顺序如下所示
也就是会先爬取第一个大类,第一个打雷全部爬取完成之后爬取第二个打类……
在爬虫开发的过程中,应该选择深度优先还是广度优先呢?这就需要根据被爬取的数据来进行选择了。
例如要爬取某网站全国所有的餐馆信息和每个餐馆的订单信息。假设使用深度优先算法,那么先从某个链接爬到了餐馆A,再立刻去爬餐馆A的订单信息。由于全国有十几万家餐馆,全部爬完可能需要12小时。这样导致的问题就是,餐馆A的订单量可能是早上8点爬到的,而餐馆B是晚上8点爬到的。它们的订单量差了12小时。
而对于热门餐馆来说,12小时就有可能带来几百万的收入差距。这样在做数据分析时,12小时的时间差就会导致难以对比A和B两个餐馆的销售业绩。
相对于订单量来说,餐馆的数量变化要小得多。所以如果采用广度优先搜索,先在半夜0点到第二天中午12点把所有的餐馆都爬取一遍,第二天下午14点到20点再集中爬取每个餐馆的订单量。这样做,只用了6个小时就完成了订单爬取任务,缩小了由时间差异致的订单量差异。同时由于店铺隔几天抓一次影响也不大,所以请求量也减小了,使爬虫更难被网站发现。
所以在爬取数据量大的情况下建议使用广度优先,数据量较小的情况下建议使用深度优先
需求
从http: //www.kanunu8.com / book3/6879爬取《动物农场》所有章节的网址,再通过一个多线程爬虫将每一章的内容爬取下来。在本地创建一个“动物农场”文件夹,并将小说中的每一章分别保存到这个文件夹中。每一章保存为一个文件。
需要读取每一章节的内容就需要拿到每一张的地址,我们可以发现每一章节的地址就是下图中红色圈住的地方不一致而已不一样而已,我们只需要拿到最后的内容即可
url = 'http://www.kanunu8.com/book3/6879/'
html = requests.get(url).content.decode('gbk')
title_url_list = re.findall('a href="(\d+.html)">', html, re.S)
接下来我们需要拼接处每个章节的地址
title_url = []
for i in title_url_list:
title_url.append(url + i)
随便点击一个地址
将上面的代码写到一个函数里面
import requests from multiprocessing.dummy import Pool import lxml.html import re url = 'http://www.kanunu8.com/book3/6879/' ''' 输入网页网址,输出page_text ''' def get_source(url): return requests.get(url).content.decode('gbk') ''' 构建一个方法获取每一个章节的url,并且返回一个list ''' def get_title_url(html): title_url_list = re.findall('a href="(\d+.html)">', html, re.S) # 拼接出每个章节地址 title_url = [] for i in title_url_list: title_url.append(url + i) return title_url if __name__ == '__main__': html = get_source(url) title_url = get_title_url(html)
现在构建一个处理单个章节的函数
def get_content(html):
chapter_name = re.search('size="4">(.*?)<', html, re.S).group(1)
text_block = re.search('<p>(.*?)</p>', html, re.S).group(1)
text_block = text_block.replace('<br />', '')
return chapter_name, text_block
在写一个保存函数
def save(chapter, article):
os.makedirs('动物农场', exist_ok=True) # 如果没有"动物农场文件夹,就创建一个,如果有,则什么都不做"
with open(os.path.join('动物农场', chapter + '.txt'), 'w', encoding='utf-8') as f:
f.write(article)
编写一个函数来调用上述的几个函数
def query_content(url):
content_html = get_source(url)
chapter_name, text_block = get_content(content_html)
save(chapter_name, text_block)
运行结果如下所示
完整代码如下
import requests from multiprocessing.dummy import Pool import lxml.html import os import re url = 'http://www.kanunu8.com/book3/6879/' ''' 输入网页网址,输出page_text ''' def get_source(url): return requests.get(url).content.decode('gbk') ''' 构建一个方法获取每一个章节的url,并且返回一个list ''' def get_title_url(html): title_url_list = re.findall('a href="(\d+.html)">', html, re.S) # 拼接出每个章节地址 title_url = [] for i in title_url_list: title_url.append(url + i) return title_url ''' 处理每一个章节 输入章节html 输出文章和标题 ''' def get_content(html): chapter_name = re.search('size="4">(.*?)<', html, re.S).group(1) text_block = re.search('<p>(.*?)</p>', html, re.S).group(1) text_block = text_block.replace('<br />', '') return chapter_name, text_block ''' 保存函数 ''' def save(chapter, article): os.makedirs('动物农场', exist_ok=True) # 如果没有"动物农场文件夹,就创建一个,如果有,则什么都不做" with open(os.path.join('动物农场', chapter + '.txt'), 'w', encoding='utf-8') as f: f.write(article) ''' 获取每一个章节的url,变成html传输个体get_content方法 并且调用save方法保存 ''' def query_content(url): content_html = get_source(url) chapter_name, text_block = get_content(content_html) save(chapter_name, text_block) if __name__ == '__main__': html = get_source(url) title_url = get_title_url(html) pool = Pool(4) pool.map(query_content, title_url)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。