文章目录
-
- 前言
- 线程锁
- retrying模块
- aiohttp模块
- requests-html模块
-
- 获取网页源码
- 获取链接
- 获取元素
- JavaScript支持!!!
前言
这两天都没更新啥哈,太忙了。
今天这篇会比较特殊一些,因为爬虫我会的也写的差不多了,但是我还有一个背后隐藏能源还没拿出来用呢!!!
今天就打开这个隐藏能源,这可是一个大佬多年呕心沥血打造的,看看我能从中学到多少东西。
线程锁
哈哈,先来就讲这个和爬虫“不太沾边”的东西,是不是不好啊。
其实是很有关系的啦。如果你要靠自己大批量爬取内容,肯定是离不开多线程工作了嘛,要是单线程那速度你会很感动的。
但是多线程就有一个通病,资源竞态,这是跑不掉的。
那怎么解决这个问题,最直接的一个办法就是上锁。
all_urls = [] #url数组
g_lock = threading.Lock() #初始化一个锁
while len(all_urls) > 0 :
g_lock.acquire() #在访问all_urls的时候,需要使用锁机制
page_url = all_urls.pop() #通过pop方法移除最后一个元素,并且返回该值
g_lock.release() #使用完成之后及时把锁给释放,方便其他线程使用
······
retrying模块
当我们用 request 发起网络请求,时不时会遇到超时,当然不可能让这个请求一直阻塞,一般会设置一个超时时间,用 try except 抛出异常,避免程序中断。我们日常访问某网站时,有打不开的情况都会多刷新几次。因此,我们也需要让 python 进行重试。而 retrying 模块应运而生
为了表现 retrying 的重试功能,我们故意请求一个不规范的链接,如 www.baidu.com ,由于没有带 http 协议,request 会报错,从而触发 retrying 重试:
import requests
from retrying import retry
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36"
}
@retry(stop_max_attempt_number=3) # 表示重试以下代码三次
def _parse_url(url):
print("-" * 30)
response = requests.get(url, headers=headers, timeout=3)
assert response.status_code == 200
return response.content.decode()
def parse_url(url):
try:
html_str = _parse_url(url)
except:
html_str = None
return html_str
if __name__ == "__main__":
url = 'www.baidu.com'
print(parse_url(url))
接着,你再把http加上去试试看。
aiohttp模块
在 Python 众多的 HTTP 客户端中,最有名的莫过于requests、aiohttp和httpx(说实话,今天之前,我只知道requests和那个urllib)。
在不借助其他第三方库的情况下,requests只能发送同步请求;aiohttp只能发送异步请求;httpx既能发送同步请求,又能发送异步请求。
同步和异步的概念:同步是串行,异步是并发。同步请求就是请求完了一个再请求一个,异步请求就是不管有没有结果回来,继续往下请求再说。
实在不知道要怎么说,来段代码吧:
import aiohttp
import asyncio
async def fetch_img_url(num):
url = f'http://bbs.fengniao.com/forum/forum_101_{num}_lastpost.html' # 字符串拼接
# 或者直接写成 url = 'http://bbs.fengniao.com/forum/forum_101_1_lastpost.html'
print(url)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6726.400 QQBrowser/10.2.2265.400',
}
async with aiohttp.ClientSession() as session: # 创建一个会话,用于保持连接。保持连接的好处我们心照不宣哈
# 获取轮播图地址
async with session.get(url, headers=headers) as response:
try:
html = await response.text() # 获取到网页源码 # await:等待网页数据返回
print(html)
except Exception as e:
print("基本错误")
print(e)
loop = asyncio.get_event_loop() #创建线程
tasks = asyncio.ensure_future(fetch_img_url(1))
results = loop.run_until_complete(tasks) #负责安排tasks中的任务
# task可以是单独的函数,也可以是列表
httpx我希望各位可以回去自行查找资料,我不急,我开班了再说这个。
这里带上一个大佬的评论:
tasks并不是只能开1024个协程,只是最多只能同时运行1024个协程(实际上会低于这个数),因为asycnio内部使用的是select ,而select的其中一个局限性就是最多只能有1024个fd(文件描述法)。官方不使用poll和epoll,我猜测是为了Windows的兼容性,因为后面两个虽然强大但不支持Windows。还有开多少个协程都只是充分利用CPU,你放心,它只是持续处于亢奋状态,应该不会吃不消。
受教了。
requests-html模块
requests-html和其他解析HTML库最大的不同点在于HTML解析库一般都是专用的,所以我们需要用另一个HTTP库先把网页下载下来,然后传给那些HTML解析库。而requests-html自带了这个功能,所以在爬取网页等方面非常方便。
不多废话,直接看它能干嘛,怎么干!!!
获取网页源码
from requests_html import HTMLSession
session = HTMLSession()
r = session.get('url')
// 查看页面内容
print(r.html.html)
获取链接
# 获取链接
print(r.html.links) # 获取所有链接
print(r.html.absolute_links) # 获取全部绝对链接
获取元素
这里只讲Xpath,这需要另一个函数xpath的支持,它有4个参数如下:
- selector,要用的XPATH选择器;
- clean,布尔值,如果为真会忽略HTML中style和script标签造成的影响(原文是sanitize,大概这么理解);
- first,布尔值,如果为真会返回第一个元素,否则会返回满足条件的元素列表;
- _encoding,编码格式。
print(r.html.xpath("//div[@id='menu']", first=True).text)
print(r.html.xpath("//div[@id='menu']/a"))
print(r.html.xpath("//div[@class='content']/span/text()"))
懂得都懂,不再赘述,不懂私聊。
JavaScript支持!!!
这个牛逼了,好多次被JavaScript拦在了门口。
有些网站是使用JavaScript渲染的,这样的网站爬取到的结果只有一堆JS代码,这样的网站requests-html也可以处理,关键一步就是在HTML结果上调用一下render函数,它会在用户目录(默认是~/.pyppeteer/)中下载一个chromium,然后用它来执行JS代码。下载过程只在第一次执行,以后就可以直接使用chromium来执行了。唯一缺点就是chromium下载实在太慢了。
>>> r = session.get('http://python-requests.org/')
>>> r.html.render()
[W:pyppeteer.chromium_downloader] start chromium download.
Download may take a few minutes.
[W:pyppeteer.chromium_downloader] chromium download done.
[W:pyppeteer.chromium_downloader] chromium extracted to: C:\Users\xxxx\.pyppeteer\local-chromium\571375
>>> r.html.search('Python 2 will retire in only {months} months!')['months']
'<time>25</time>'
render函数还有一些参数,顺便介绍一下(这些参数有的还有默认值,直接看源代码方法参数列表即可):
- retries: 加载页面失败的次数
- script: 页面上需要执行的JS脚本(可选)
- wait: 加载页面钱的等待时间(秒),防止超时(可选)
- scrolldown: 页面向下滚动的次数
- sleep: 在页面初次渲染之后的等待时间
- reload: 如果为假,那么页面不会从浏览器中加载,而是从内存中加载
- keep_page: 如果为真,允许你用r.html.page访问页面
比如说简书的用户页面上用户的文章列表就是一个异步加载的例子,初始只显示最近几篇文章,如果想爬取所有文章,就需要使用scrolldown配合sleep参数模拟下滑页面,促使JS代码加载所有文章。
下次再遇到这种情况就有据可循了。
先翻它四分钟之一,饿都饿死了,下篇再写吧。