面试过程总会被问到“HTTP协议如何工作?“,”一次完整的http请求是经历什么过程“...... 确实此题能衡量程序员的功底,如果你回答非常完整,说明你对网络请求过程是非常了解的,对大流量和大并发场景你就很清楚如何进行优化,本篇文章从输入URL到浏览器显示页面发生了什么这视角大体了解一下,当你在浏览器地址栏输入网址后浏览器是怎么把最终的页面呈现出来的呢?这个过程从程序员理解的角度可以分为以下几个步骤:
我先给大家看看整体的请求过程,为能更好地让读者明白,作者会分期完整介绍以下过程。
请求整体过程
域名解析 -> 发起TCP的3次握手 -> 建立TCP连接后发起http请求 -> 服务器响应http请求->浏览器得到html代码 -> 浏览器解析html代码同时请求html代码中的资源(如js、css、图片等) -> 浏览器对页面进行渲染呈现给用户。
获取内容请求
以上过程大致进行分析细节,以方便大家更加详细地认识整体的过程,但是有些过程没有能理解透彻并且过程比较复杂未能提炼通俗易懂语言给大家分析,不过后续会不断分析给大家的。
1.域名解析
我们以www.cnblogs.com为例:请问www.cnblogs.com这个域名的IP地址是多少?
目的是通过域名地址转化到资源URL的IP地址,对用户使用域名是为了方便记忆,但是为了让计算机理解这个地址还需要把它解析为IP地址,当用户在地址栏输入URL中,浏览器会首先搜索浏览器自身的DNS缓存,先看自身的缓存中是否存在没有过期对应的条目,如果找到且没有过期则停止搜索解析到此结束,如果没有浏览器会搜索操作系统的DNS缓存,在操作系统也没有找到,那么尝试读hosts文件,看看里面是否配置对应域名的IP地址,如果在hosts文件中也没有找到对应的条目,浏览器就会发起一次DNS的系统调用,这过程是通过UDP协议向DNS的53端口发起请求递归迭代请求,这过程有运营商DNS服务提供给我们,运营商的DNS服务器必须得提供给我们对应域名的IP地址,先向本地配置的首选DNS服务器发起域名解析请求(一般是由电信运营商提供或者各大互联网厂商提供的DNS服务器)运营商的DNS服务器首先查找自身的缓存,找到对应的条目,且没有过期,则解析成功。如果没有找到对应的条目,则运营商的DNS代浏览器发起迭代DNS解析请求,它首先是会找根域的DNS的IP地址(这台DNS服务器都内置13台根域的DNS的IP地址),找到根域的DNS地址,就会向其发起请求,来一场寻址之旅:
运营商DNS:请问www.cnblogs.com这个域名的IP地址是多少呢?
根域DNS:你一个顶级域com域的一个域名,我不知道这个域名的IP地址,但是我知道com域的IP地址,你去找它去问一问呢?
运营商DNS:请问www.cnblogs.com这个域名的IP地址是多少呢?
COM域:我不知道www.cnblogs.com这个域名的IP地址,但是我知道cnblogs.com这个域的DNS地址,你去找它去去问一问呢?
cnblogs.com域名的DNS:这个时候cnblogs.com域的DNS服务器一查,诶,果真在我这里,一般就是由域名注册商提供的,像万网,新网等。
于是就把找到的结果发送给运营商的DNS服务器,这个时候运营商的DNS服务器就拿到了域名对应的IP地址,并返回给操作系统内核,内核又把结果返回给浏览器,终于浏览器拿到了。
域名解析流程
备注:
浏览器:可以使用 chrome://net-internals/#dns 来进行查看
操作系统:Mac的dns缓存查询 nslookup www.baidu.com
“
感觉只要学过Python爬虫的同学应该都知道requests这个库吧,它在我们的Python爬虫任务中应该是最常用的一个库了!今天跟大家分享的这个模块requests_html,他的作者和前者是同一人!这是一个解析HTML的库,用起来和requests一样方便,下面就来介绍一下它!
”
#!/usr/bin/env python3
# coding : utf-8
# Author : xiao qiang
# 微信公众号 : xiaoqiangclub
# Software : PyCharm
# File : test.py
# Time : 2021/5/29 7:57
from requests_html import HTMLSession
if __name__ == '__main__':
url = 'https://wwww.baidu.com'
session = HTMLSession() # 获取实例化session对象
r = session.get(url) # 这里的请求和requests的几乎一样!同样可以根据需要添加headers等参数
#!/usr/bin/env python3
# coding : utf-8
# Author : xiao qiang
# 微信公众号 : xiaoqiangclub
# Software : PyCharm
# File : test.py
# Time : 2021/5/29 7:57
from requests_html import HTMLSession
if __name__ == '__main__':
url = 'https://wwww.baidu.com'
session = HTMLSession() # 获取实例化session对象
r = session.get(url) # 这里的请求和requests的几乎一样!同样可以根据需要添加headers等参数
# 获取html页面
# html = r.content.decode() # requests方式
get_html = r.html.html # requests_html中的方法
print(get_html[:15], '...')
# 快速获取链接
pprint(r.html.links) # 获取html中的链接(href属性)
pprint(r.html.absolute_links) # 会自动拼接url生成绝对链接
def xpath(self, selector: str, *, clean: bool = False, first: bool = False, _encoding: str = None) -> _XPath:
- selector,要用的 xpath选择器;
- clean,布尔值,如果为True,会清除HTML中style和script标签;
- first,布尔值,如果为True,会返回第一个元素,否则会返回满足条件的元素列表;
- _encoding,编码格式。
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a'))
pprint(r.html.xpath('//li[@class="hotsearch-item odd"]/a', first=True).text)
def find(self, selector: str = "*", *, containing: _Containing = None, clean: bool = False, first: bool = False, _encoding: str = None) -> _Find:
- selector,要用的CSS选择器;
- clean,布尔值,如果为True,会清除HTML中style和script标签;
- containing,如果设置该属性,只返回包含该属性文本的标签;
- first,布尔值,如果为True,会返回第一个元素,否则会返回满足条件的元素列表;
- _encoding,编码格式。
pprint(r.html.find('a.mnav'))
pprint(r.html.find('a.mnav', first=True).text)
pprint(r.html.find('a.mnav')[0].text)
pprint(r.html.find('a.mnav')[0].attrs)
pprint(r.html.find('a.mnav')[0].html)
def search(self, template: str) -> Result:
# 只有一个参数
template: 就是要检索的内容,这里使用英文状态的 {} 来获取内容,有点类似正则里面的 ()
ret = r.html.find('a.mnav')[0].search('新{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0])
ret=r.html.find('a.mnav')[0].search_all('新{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])
ret=r.html.search_all('百度{}')
pprint(ret)
pprint(type(ret))
pprint(ret[0][0])
search补充
>>> from requests_html import HTML
>>> doc = """<a href='https://www.baidu.com'>"""
>>> html = HTML(html=doc)
>>> html.links
{'https://www.baidu.com'}
# 和上面一段代码接起来
>>> script = """
() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio,
}
}
"""
>>> val = html.render(script=script, reload=False) # render()方法 后面会讲
>>> print(val)
{'width': 800, 'height': 600, 'deviceScaleFactor': 1}
>>> print(html.html)
<html><head></head><body><a href="https://www.baidu.com"></a></body></html>
>>> 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>'
def render(self, retries: int = 8, script: str = None, wait: float = 0.2, scrolldown=False, sleep: int = 0, reload: bool = True, timeout: Union[float, int] = 8.0, keep_page: bool = False):
- retries: 加载页面失败的次数
- script: 页面上需要执行的JS脚本(可选)
- wait: 加载页面前等待的时间(秒),防止超时(可选)
- scrolldown: 页面向下滚动的次数(整数)
- sleep: 在页面初次渲染之后的等待时间
- reload: 如果为False,那么页面不会从浏览器中加载,而是从内存中加载,只有设置为True才会在浏览器中渲染JS
- keep_page: 如果为True,允许您使用 r.html.page 访问浏览器页面
def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
fetch: 一个布尔型参数,默认为False:直接返回下一页的 url地址;
如果设置为True:则直接返回下一页的 HTML对象
DEFAULT_NEXT_SYMBOL = ['next', 'more', 'older']
# next()方法
def next(self, fetch: bool = False, next_symbol: _NextSymbol = DEFAULT_NEXT_SYMBOL) -> _Next:
"""Attempts to find the next page, if there is one. If ``fetch``
is ``True`` (default), returns :class:`HTML <HTML>` object of
next page. If ``fetch`` is ``False``, simply returns the next URL.
"""
def get_next():
candidates = self.find('a', containing=next_symbol) # 寻找 包含字段'next', 'more', 'older' 的a标签
def __init__(self, loop=None, workers=None, mock_browser: bool = True, *args, **kwargs):
loop: 使用的Asyncio循环。
workers: 用于执行异步调用的线程数量。如果不传递,它将默认为电脑处理器数量乘以5
>>> async def get_pyclock():
... r = await asession.get('https://pythonclock.org/')
... await r.html.arender()
... return r
...
>>> results = asession.run(get_pyclock, get_pyclock, get_pyclock) # 这里作者将同一个页面使用异步方式进行了3次渲染,但是实际上使用的时间并不是平时的3倍!可能只是比平时渲染一个页面多花了一点时间而已!这就是异步的好处!
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author : xiao qiang
# @WeChat : xiaoqiangclub
# @Software : PyCharm
# @File : test002.py
# @Time : 2021/5/30 19:48
from requests_html import AsyncHTMLSession
aSession = AsyncHTMLSession()
async def test(tt, yy):
r = await aSession.get('https://www.baidu.com/')
await r.html.arender()
print('-{}-{}-'.format(tt, yy))
return r
ret1 = aSession.run(lambda: test('1', 'a'))
ret2 = aSession.run(lambda: test('2', 'b'))
ret3 = aSession.run(lambda: test('3', 'c'))
print(ret1)
print(ret2)
print(ret3)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
# @Author : xiao qiang
# @WeChat : xiaoqiangclub
# @Software : PyCharm
# @File : test002.py
# @Time : 2021/5/30 19:48
from requests_html import AsyncHTMLSession
aSession=AsyncHTMLSession()
async def test(tt, yy):
r=await aSession.get('https://www.baidu.com/')
await r.html.arender()
print('-{}-{}-'.format(tt, yy))
return r
# ret1=aSession.run(lambda: test('1', 'a'))
# ret2=aSession.run(lambda: test('2', 'b'))
# ret3=aSession.run(lambda: test('3', 'c'))
# print(ret1)
# print(ret2)
# print(ret3)
#
test_dict={
'1': 'a',
'2': 'b',
'3': 'c'
}
tasks=[lambda i=i, y=y: test(i, y) for i, y in
test_dict.items()] # lambda传参误区参考文章:https://www.jianshu.com/p/58ebd1618556
ret=aSession.run(*tasks) # 注意前面有个 *,不可少!# 参考文章:https://www.jianshu.com/p/58ebd1618556
print(ret)
>>> from requests_html import AsyncHTMLSession
>>> asession = AsyncHTMLSession()
>>> async def get_pythonorg():
... r = await asession.get('https://python.org/')
... return r
...
>>> async def get_reddit():
... r = await asession.get('https://reddit.com/')
... return r
...
>>> async def get_google():
... r = await asession.get('https://google.com/')
... return r
...
>>> results = asession.run(get_pythonorg, get_reddit, get_google)
>>> results # check the requests all returned a 200 (success) code
[<Response [200]>, <Response [200]>, <Response [200]>]
>>> # Each item in the results list is a response object and can be interacted with as such
>>> for result in results:
... print(result.html.url)
...
https://www.python.org/
https://www.google.com/
https://www.reddit.com/
from requests_html import HTMLSession, HTML, AsyncHTMLSession
from pprint import pprint
class DouBanTest:
def __init__(self):
self.start_url = 'https://movie.douban.com/chart' # 豆瓣电影排行榜url
self.js_url = 'https://movie.douban.com/explore#!type=movie&tag=%E7%83%AD%E9%97%A8&sort=recommend&page_limit=20&page_start=0'
self.session = HTMLSession() # 实例化session
self.aSession = AsyncHTMLSession() # 实例化异步session
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
}
def get_response(self, url):
"""获取响应,并返回requests_html中的HTML对象"""
r = self.session.get(url, headers=self.headers)
# print(r)
return r.html
# 快速获取页面中的url
def fast_get_urls(self):
"""快速获取页面中的url"""
html = self.get_response(self.start_url)
# HTML的 links属性 可以快速获取到页面中 a标签中的href属性
urls = html.links
# pprint(urls)
# HTML的 absolute_links属性 可以快速获取到页面中 a标签中的href属性,并返回绝对url地址
absolute_urls = html.absolute_links
pprint(absolute_urls)
# 清洗数据(提取数据)
def get_data_by_xpath(self):
"""使用xpath获取数据"""
html = self.get_response(self.start_url)
a_list = html.xpath('//table//div/a')
# pprint(a_list)
# 提取它的标题和url
movies_info = dict()
for a in a_list:
title = a.text # 获取标题(文本)
# print(title)
movie_url = a.attrs.get('href') # 使用 attrs 来解析element元素,并获得一个字典
# print(movie_url)
# print('-----')
movies_info[title] = movie_url
pprint(movies_info)
# 清洗数据(提取数据)
def get_data_by_css(self):
"""使用css获取数据"""
html = self.get_response(self.start_url)
a_list = html.find('tr[class="item"] div a') # 参考 css选择器 语法
# pprint(a_list)
# 提取它的标题和url
movies_info = dict()
for a in a_list:
title = a.text # 获取标题(文本)
# print(title)
movie_url = a.attrs.get('href') # 使用 attrs 来解析element元素,并获得一个字典
# print(movie_url)
# print('-----')
movies_info[title] = movie_url
pprint(movies_info)
# 清洗数据(提取数据)
def get_data_by_re(self):
"""使用css获取数据"""
html = self.get_response(self.start_url)
# search() 获取第一条匹配的数据
# first_url = html.search('a href="{}"') # 参数可以参考正则,获取第一条匹配的数据
# pprint(first_url)
# search_all() 获取所有满足条件的数据列表
# url_list = html.search_all('a h{}f="{}"')
url_list = html.search_all('a h{title}f="{url}"') # 对取值方式进行命名,返回一个列表
# pprint(url_list)
#
# 提取数据
for url in url_list:
print(url)
print(url['title']) # 使用 result[name] 进行取值
print(url['url'])
# print(url[0])
# print(url[1])
print('----------')
# HTML类
def use_HTML(self):
"""使用HTML模块处理文档"""
html_str = '<a class="nbg" href="https://movie.douban.com/subject/3099221/" title="活死人军团">'
html = HTML(html=html_str)
# links
print(html.links)
# search()
print(html.search('href="{}"'))
# 加载JS页面
def load_js(self):
html = self.get_response(self.js_url)
# 使用一个 render()方法 来加载js(实际上使用这个pyppeteer)
# html.render(wait=3) # js加载
print(html.html)
async def send_requests_ues_async(self, url):
"""发送异步请求"""
"""获取响应,并返回requests_html中的HTML对象"""
r = await self.aSession.get(url, headers=self.headers)
# print(r)
return r.html
def get_response_by_async(self):
url_list = [
'https://www.baidu.com',
'https://www.qq.com',
'https://www.163.com',
]
tasks = [lambda url=url: self.send_requests_ues_async(url) for url in url_list]
ret = self.aSession.run(*tasks) # 返回的是一个HTML对象列表
# print(ret)
# print(ret[0].html)
for html in ret:
print(html)
async def load_js_use_async(self, url):
"""异步加载js"""
html = await self.send_requests_ues_async(url)
# 异步加载js
await html.arender()
return html
def get_js_by_async(self):
# ret = self.aSession.run(self.load_js_use_async)
#
# print(ret[0].html)
url_list = [
'https://www.baidu.com',
'https://www.qq.com',
'https://www.163.com',
]
tasks = [lambda url=url: self.load_js_use_async(url) for url in url_list]
ret = self.aSession.run(*tasks) # 返回的是一个HTML对象列表
# print(ret)
# print(ret[0].html)
for html in ret:
print(html)
if __name__ == '__main__':
test = DouBanTest()
# test.get_data_by_xpath()
# test.get_data_by_css()
# test.fast_get_urls()
# test.get_data_by_re()
# test.use_HTML()
# test.load_js()
# test.get_response_by_async()
test.get_js_by_async()
【本文由 "XiaoqiangClub" 发布,2021年6月17日】
于安全和隐私的原因,web 应用程序不能直接访问用户设备上的文件。如果需要读取一个或多个本地文件,可以通过使用input file和FileReader来实现。在这篇文章中,我们将通过一些例子来看看它是如何工作的。
由于浏览器中的 JS 无法从用户的设备访问本地文件,我们需要为用户提供一种方法来选择一个或多个文件供我们使用。这可以通过文件选择器<input type='fule' />来完成。
<input type="file" id="fileInput">
如果想允选择多个文件,可以添加multiple属性:
<input type="file" id="fileInput" multiple>
我们可以通过change事件来监听文件的选择,也可以添加另一个 UI 元素让用户显式地开始对所选文件的处理。
input file 具有一个files属性,该属性是File对象的列表(可能有多个选择的文件)。
File对象如下所示:
读取文件,主要使用的是[FileReader][1]类。
「该对象拥有的属性:」
「FileReader.error」 :只读,一个DOMException,表示在读取文件时发生的错误 。
「FileReader.readyState」:只读 表示 FileReader 状态的数字。取值如下:
常量名值描述EMPTY0还没有加载任何数据LOADING1数据正在被加载DONE2已完成全部的读取请求
「FileReader.result」:只读,文件的内容。该属性仅在读取操作完成后才有效,数据的格式取决于使用哪个方法来启动读取操作。
「该对象拥有的方法:」
readAsText(file, encoding):以纯文本形式读取文件,读取到的文本保存在result属性中。第二个参数代表编码格式。
readAsDataUrl(file):读取文件并且将文件以数据URI的形式保存在result属性中。
readAsBinaryString(file):读取文件并且把文件以字符串保存在result属性中。
readAsArrayBuffer(file):读取文件并且将一个包含文件内容的ArrayBuffer保存咋result属性中。
FileReader.abort():中止读取操作。在返回时,readyState属性为DONE。
「文件读取的过程是异步操作,在这个过程中提供了三个事件:progress、error、load事件。」
progress:每隔50ms左右,会触发一次progress事件。
error:在无法读取到文件信息的条件下触发。
load:在成功加载后就会触发。
在下面的示例中,我们将使用readAsText和readAsDataURL方法来显示文本和图像文件的内容。
为了将文件内容显示为文本,change需要重写一下:
首先,我们要确保有一个可以读取的文件。如果用户取消或以其他方式关闭文件选择对话框而不选择文件,我们就没有什么要读取和退出函数。
然后我们继续创建一个FileReader。reader的工作是异步的,以避免阻塞主线程和 UI 更新,这在读取大文件(如视频)时非常重要。
reader发出一个'load'事件(例如,类似于Image对象),告诉我们的文件已经读取完毕。
reader将文件内容保存在其result属性中。此属性中的数据取决于我们使用的读取文件的方法。在我们的示例中,我们使用readAsText方法读取文件,因此result将是一个文本字符串。
如果我们想要显示图像,将文件读取为字符串并不是很有用。FileReader有一个readAsDataURL方法,可以将文件读入一个编码的字符串,该字符串可以用作<img>元素的源。本例的代码与前面的代码基本相同,区别是我们使用readAsDataURL读取文件并将结果显示为图像:
1)由于安全和隐私的原因,JavaScript 不能直接访问本地文件。
2)可以通过 input 类型为 file 来选择文件,并对文件进行处理。
3) file input 具有带有所选文件的files属性。
4) 我们可以使用FileReader来访问所选文件的内容。
作者: Martin Splitt 译者:前端小智 来源:dev
原文:https://dev.to/g33konaut/reading-local-files-with-javascript-25hn
*请认真填写需求信息,我们会在24小时内与您取得联系。