面試過程總會被問到“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事件?!?/strong>
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小時內與您取得聯系。