Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 亚洲欧美在线观看视频,91好色视频,一级伦奸视频

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          Chrome瀏覽器完美保存整個網頁-頁面離線保存

          hrome瀏覽器完美保存整個網頁---頁面離線保存

          SingleFile 是一個瀏覽器插件兼容 Chrome、Firefox(桌面端和移動端)、Microsoft Edge、Vivaldi、Brave、Waterfox、Yandex 和 Opera 瀏覽器。它可以幫助你將一個完整的網頁保存為一個單一的 HTML 文件。另一個版本SingleFileZ 是一款可以把一個網頁包括圖片、樣式完整打包壓縮后保存為 HTML 的瀏覽器擴展,并且能夠讓瀏覽器實現自解壓。SingleFileZ很有意思,SingleFileZ 與 SingleFile 功能完全相同,只不過增加了壓縮功能,使用 SingleFile 下載后的單一 HTML 文件為 627KB,而使用 SingleFileZ 下載后的單一 HTML 文件為 265 KB。而更有意思的是,可以直接使用壓縮工具打開由 SingleFileZ 生成的 HTML 文件,不過要打開這個壓縮 HTML 文件,需要 Chrome 啟動時添加 –allow-file-access-from-files 參數。

          下載使用

          SingleFile在主流的瀏覽器插件商店可用:

          • Firefox: https://addons.mozilla.org/firefox/addon/single-file
          • Chrome: https://chrome.google.com/extensions/detail/mpiodijhokgodhhofbcjdecpffjipkle
          • Microsoft Edge: https://microsoftedge.microsoft.com/addons/detail/efnbkdcfmcmnhlkaijjjmhjjgladedno

          也可以下載插件的 zip 包 – https://github.com/gildas-lormeau/SingleFile/archive/master.zip,拖曳到瀏覽器插件管理界面進行安裝。

          SingleFile的使用非常簡單,等待網頁完全加載完畢,點擊插件工具欄上的 SingleFile 按鈕即可保存頁面,下載的 HTML 文件保存在瀏覽器設置的本地文件夾。在處理一個頁面時,你可以再次點擊該按鈕來取消該動作。

          額外的功能

          • 選擇保存整個標簽頁面、選定的內容、選定的框架
          • 一鍵處理多個標簽頁并選擇保存
          • 給網頁添加高亮字體、筆記、移除內容后保存
          • 自動化下載
          • 自定義保存到 Google Drive 或 GitHub

          GitHub 地址:

          • https://github.com/gildas-lormeau/SingleFile
            依托于網站的互聯網內容基于所在服務器的穩定性,當我們需要不時參考一些網頁時,最好使用 SingleFile 將它們保存到本地;SingleFile 可以一比一復制下載任意網頁到單個 HTML,方便管理又不是很占內存,實在是一款數據安全神器。還有另一個不錯的Joplin值得推薦!本文不介紹了。

          S 鼠標框選(頁面選擇)時返回對應的 HTML 或文案內容

          一、需求背景

          1、項目需求

          當用戶進行鼠標框選選擇了頁面上的內容時,把選擇的內容進行上報。

          2、需求解析

          雖然這需求就一句話的事,但是很顯然,沒那么簡單...

          因為鼠標框選說起來簡單,就是選擇的內容,但是這包含很多中情況,比如:只選擇文案、選擇圖片、選擇輸入框、輸入框中的內容選擇、iframe、等。

          簡單總結,分為以下幾點:

          1. 選擇文案時
          2. 選擇圖片、svg、iframe、video、audio 等標簽時
          3. 選擇 input、select、textarea 等標簽時
          4. 選擇 input、textarea 標簽內容時
          5. 選擇類似 字符時
          6. 鍵盤全選時
          7. 鼠標右鍵選擇
          8. 以上各模塊結合時
          9. 當包含標簽的時候,返回 html 結構,只有文本時返回文本內容

          二、技術要點

          鼠標框選包含以下幾點:

          1. debounce 防抖
          2. addEventListener 事件監聽
          3. Range 對象
          4. Selection 對象

          1、debounce

          老生常談的技術點了,這里不能用節流,因為肯定不能你鼠標選擇的時候,隔一段時間返回一段內容,肯定是選擇之后一起返回。

          這里用 debounce 主要也是用在事件監聽和事件處理上。

          • 【debounce 掘金】
          • 【debounce CSDN】

          2、addEventListener

          事件監聽,因為鼠標選擇,不僅僅是鼠標按下到鼠標抬起,還包括雙擊、右鍵、全選。

          需要使用事件監聽對事件作處理。

          • 【addEventListener MDN】

          3、Range

          Range 接口表示一個包含節點與文本節點的一部分的文檔片段。

          Range 是瀏覽器原生的對象。

          3.1. 創建 Range 實例,并設置起始位置

          <body>
            <ul>
              <li>Vite</li>
              <li>Vue</li>
              <li>React</li>
              <li>VitePress</li>
              <li>NaiveUI</li>
            </ul>
          </body>
          <script>
            // 創建 Range 對象
            const range = new Range()
            const liDoms = document.querySelectorAll("li");
            // Range 起始位置在 li 2
            range.setStartBefore(liDoms[1]);
            // Range 結束位置在 li 3
            range.setEndAfter(liDoms[2]);
            // 獲取 selection 對象
            const selection = window.getSelection();
            // 添加光標選擇的范圍
            selection.addRange(range);
          </script>


          可以看到,選擇內容為第二行和第三行

          3.1.1 瀏覽器兼容情況


          3.2. Range 屬性

          1. startContainer:起始節點。
          2. startOffset:起始節點偏移量。
          3. endContainer:結束節點。
          4. endOffset:結束節點偏移量。
          5. collapsed:范圍的開始和結束是否為同一點。
          6. commonAncestorContainer:返回完整包含 startContainer 和 endContainer 的最深一級的節點。

          3.2.1. 用我們上面創建的實例來看下 range 屬性的值


          3.2.2. 如果我們只選擇文本內容時

          只選擇 li 中的 itePres


          可以看出 range 屬性對應的值


          3.3. Range 方法

          1. cloneContents():復制范圍內容,并將復制的內容作為 DocumentFragment 返回。
          2. cloneRange():創建一個具有相同起點/終點的新范圍, 非引用,可以隨意改變,不會影響另一方。
          3. collapse(toStart):如果 toStart=true 則設置 end=start,否則設置 start=end,從而折疊范圍。
          4. compareBoundaryPoints(how, sourceRange):兩個范圍邊界點進行比較,返回一個數字 -1、0、1。
          5. comparePoint(referenceNode, offset):返回-1、0、1具體取決于 是 referenceNode 在 之前、相同還是之后。
          6. createContextualFragment(tagString):返回一個 DocumentFragment。
          7. deleteContents():刪除框選的內容。
          8. extractContents():從文檔中刪除范圍內容,并將刪除的內容作為 DocumentFragment 返回。
          9. getBoundingClientRect():和 dom 一樣,返回 DOMRect 對象。
          10. getClientRects():返回可迭代的對象序列 DOMRect。
          11. insertNode(node):在范圍的起始處將 node 插入文檔。
          12. intersectsNode(referenceNode):判斷與給定的 node 是否相交。
          13. selectNode(node):設置范圍以選擇整個 node。
          14. selectNodeContents(node):設置范圍以選擇整個 node 的內容。
          15. setStart(startNode, startOffset):設置起點。
          16. setEnd(endNode, endOffset):設置終點。
          17. setStartBefore(node):將起點設置在 node 前面。
          18. setStartAfter(node):將起點設置在 node 后面。
          19. setEndBefore(node):將終點設置為 node 前面。
          20. setEndAfter(node):將終點設置為 node 后面。
          21. surroundContents(node):使用 node 將所選范圍內容包裹起來。

          3.4. 創建 Range 的方法

          3.4.1. Document.createRange

          const range = document.createRange();

          3.4.2. Selection 的 getRangeAt() 方法

          const range = window.getSelection().getRangeAt(0)

          3.4.3. caretRangeFromPoint() 方法

          if (document.caretRangeFromPoint) {
              range = document.caretRangeFromPoint(e.clientX, e.clientY);
          }

          3.4.4. Range() 構造函數

          const range = new Range()

          3.5. Range 兼容性


          4、Selection

          Selection 對象表示用戶選擇的文本范圍或插入符號的當前位置。它代表頁面中的文本選區,可能橫跨多個元素。

          4.1. 獲取文本對象

          window.getSelection()



          4.2. Selection 術語

          4.2.1. 錨點 (anchor)

          錨指的是一個選區的起始點(不同于 HTML 中的錨點鏈接)。當我們使用鼠標框選一個區域的時候,錨點就是我們鼠標按下瞬間的那個點。在用戶拖動鼠標時,錨點是不會變的。

          4.2.2. 焦點 (focus)

          選區的焦點是該選區的終點,當你用鼠標框選一個選區的時候,焦點是你的鼠標松開瞬間所記錄的那個點。隨著用戶拖動鼠標,焦點的位置會隨著改變。

          4.2.3. 范圍 (range)

          范圍指的是文檔中連續的一部分。一個范圍包括整個節點,也可以包含節點的一部分,例如文本節點的一部分。用戶通常下只能選擇一個范圍,但是有的時候用戶也有可能選擇多個范圍。

          4.2.4. 可編輯元素 (editing host)

          一個用戶可編輯的元素(例如一個使用 contenteditable 的 HTML 元素,或是在啟用了 designMode 的 Document 的子元素)。

          4.3. Selection 的屬性

          首先要清楚,選擇的起點稱為錨點(anchor),終點稱為焦點(focus)。

          1. anchorNode:選擇的起始節點。
          2. anchorOffset:選擇開始的 anchorNode 中的偏移量。
          3. focusNode:選擇的結束節點。
          4. focusOffset:選擇開始處 focusNode 的偏移量。
          5. isCollapsed:如果未選擇任何內容(空范圍)或不存在,則為 true。
          6. rangeCount:選擇中的范圍數,之前說過,除 Firefox 外,其他瀏覽器最多為1。
          7. type:類型:None、Caret、Range

          4.4. Selection 方法

          1. addRange(range): 將一個 Range 對象添加到當前選區。
          2. collapse(node, offset): 將選區折疊到指定的節點和偏移位置。
          3. collapseToEnd(): 將選區折疊到當前選區的末尾。
          4. collapseToStart(): 將選區折疊到當前選區的起始位置。
          5. containsNode(node, partlyContained): 判斷選區是否包含指定的節點,可以選擇是否部分包含。
          6. deleteFromDocument(): 從文檔中刪除選區內容。
          7. empty(): 從選區中移除所有范圍(同 `removeAllRanges()``,已廢棄)。
          8. extend(node, offset): 將選區的焦點節點擴展到指定的節點和偏移位置。
          9. getRangeAt(index): 返回選區中指定索引處的 Range 對象。
          10. removeAllRanges(): 移除所有選區中的范圍。
          11. removeRange(range): 從選區中移除指定的 Range 對象。
          12. selectAllChildren(node): 選中指定節點的所有子節點。
          13. setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset): 設置選區的起始和結束節點及偏移位置。
          14. setPosition(node, offset):collapse 的別名

          4.5. Selection 兼容性


          三、項目實現

          1、實現思路

          1. 先獲取選擇的內容,開發 getSelectContent 函數
          2. 對獲取的內容進行判斷,是否存在 selection 實例,沒有直接返回 null
          3. 判斷 selection 實例的 isCollapsed 屬性 沒有選中,對 selection 進行 toString().trim() 操作,判斷內容 有內容,直接返回 text 類型 無內容,返回 null 有選中,則判斷內容
          4. 判斷選中的內容有沒有節點 沒有節點,則和沒有選中一樣處理,進行 toString().trim() 操作,判斷內容 有內容,直接返回 text 類型 無內容,返回 null 有節點,進行 toString().trim() 操作,判斷內容 沒有內容,判斷是否有特殊節點 有 'iframe', 'svg', 'img', 'audio', 'video' 節點,返回 html 類型 有 'input', 'textarea', 'select',判斷 value 值,是否存在 存在:返回 html 類型 不存在:返回 null 沒有特殊節點,返回 null 有內容,返回 html 類型
          5. 對鼠標 mousedown、mouseup 事件和 selectionchange、contextmenu、dblclick 事件進行監聽,觸發 getSelectContent 函數
          6. 在需要的地方進行 debounce 防抖處理

          2、簡易流程圖


          2、Debounce 方法實現

          2.1. JS

          function debounce (fn, time = 500) {
            let timeout = null; // 創建一個標記用來存放定時器的返回值
            return function () {
              clearTimeout(timeout) // 每當觸發時,把前一個 定時器 clear 掉
              timeout = setTimeout(() => { // 創建一個新的 定時器,并賦值給 timeout
                fn.apply(this, arguments)
              }, time)
            }
          }

          2.2. TS

          /**
           * debounce 函數類型
           */
          type DebouncedFunction<F extends (...args: any[]) => any> = (...args: Parameters<F>) => void
          /**
           * debounce 防抖函數
           * @param {Function} func 函數
           * @param {number} wait 等待時間
           * @param {false} immediate 是否立即執行
           * @returns {DebouncedFunction}
           */
          function debounce<F extends (...args: any[]) => any>(
            func: F,
            wait = 500,
            immediate = false
          ): DebouncedFunction<F> {
            let timeout: ReturnType<typeof setTimeout> | null
            return function (this: ThisParameterType<F>, ...args: Parameters<F>) {
              // eslint-disable-next-line @typescript-eslint/no-this-alias
              const context = this
              const later = function () {
                timeout = null
                if (!immediate) {
                  func.apply(context, args)
                }
              }
              const callNow = immediate && !timeout
              if (timeout) {
                clearTimeout(timeout)
              }
              timeout = setTimeout(later, wait)
              if (callNow) {
                func.apply(context, args)
              }
            }
          }

          3、獲取選擇的文本/html 元素

          3.1. 獲取文本/html 元素

          nterface IGetSelectContentProps {
            type: 'html' | 'text'
            content: string
          }
          /**
           * 獲取選擇的內容
           * @returns {null | IGetSelectContentProps} 返回選擇的內容
           */
          const getSelectContent = (): null | IGetSelectContentProps => {
            const selection = window.getSelection()
            if (selection) {
              // 1. 是焦點在 input 輸入框
              // 2. 沒有選中
              // 3. 選擇的是輸入框
              if (selection.isCollapsed) {
                return selection.toString().trim().length
                  ? {
                      type: 'text',
                      content: selection.toString().trim()
                    }
                  : null
              }
              // 獲取選擇范圍
              const range = selection.getRangeAt(0)
              // 獲取選擇內容
              const rangeClone = range.cloneContents()
              // 判斷選擇內容里面有沒有節點
              if (rangeClone.childElementCount > 0) {
                // 創建 div 標簽
                const container = document.createElement('div')
                // div 標簽 append 復制節點
                container.appendChild(rangeClone)
                // 如果復制的內容長度為 0
                if (!selection.toString().trim().length) {
                  // 判斷是否有選擇特殊節點
                  const isSpNode = hasSpNode(container)
                  return isSpNode
                    ? {
                        type: 'html',
                        content: container.innerHTML
                      }
                    : null
                }
                return {
                  type: 'html',
                  content: container.innerHTML
                }
              } else {
                return selection.toString().trim().length
                  ? {
                      type: 'text',
                      content: selection.toString().trim()
                    }
                  : null
              }
            } else {
              return null
            }
          }
          /**
           * 判斷是否包含特殊元素
           * @param {Element} parent 父元素
           * @returns {boolean} 是否包含特殊元素
           */
          const hasSpNode = (parent: Element): boolean => {
            const nodeNameList = ['iframe', 'svg', 'img', 'audio', 'video']
            const inpList = ['input', 'textarea', 'select']
            return Array.from(parent.children).some((node) => {
              if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
              if (
                inpList.includes(node.nodeName.toLocaleLowerCase()) &&
                (node as HTMLInputElement).value.trim().length
              )
                return true
              if (node.children) {
                return hasSpNode(node)
              }
              return false
            })
          }

          3.2. 只需要文本

          /**
           * 獲取框選的文案內容
           * @returns {string} 返回框選的內容
           */
          const getSelectTextContent = (): string => {
            const selection = window.getSelection()
            return selection?.toString().trim() || ''
          }

          4、添加事件監聽

          // 是否時鼠標點擊動作
          let selectionchangeMouseTrack: boolean = false
          const selectionChangeFun = debounce(() => {
            const selectContent = getSelectContent()
            console.log('selectContent', selectContent)
            // todo... 處理上報
            selectionchangeMouseTrack = false
          })
          // 添加 mousedown 監聽事件
          document.addEventListener('mousedown', () => {
            selectionchangeMouseTrack = true
          })
          // 添加 mouseup 監聽事件
          document.addEventListener(
            'mouseup',
            debounce(() => {
              selectionChangeFun()
            }, 100)
          )
          // 添加 selectionchange 監聽事件
          document.addEventListener(
            'selectionchange',
            debounce(() => {
              if (selectionchangeMouseTrack) return
              selectionChangeFun()
            })
          )
          // 添加 dblclick 監聽事件
          document.addEventListener('dblclick', () => {
            selectionChangeFun()
          })
          // 添加 contextmenu 監聽事件
          document.addEventListener(
            'contextmenu',
            debounce(() => {
              selectionChangeFun()
            })
          )

          也可以進行封裝

          /**
           * addEventlistener function 類型
           */
          export interface IEventHandlerProps {
            [eventName: string]: EventListenerOrEventListenerObject
          }
          
          let selectionchangeMouseTrack: boolean = false
          const eventHandlers: IEventHandlerProps = {
            // 鼠標 down 事件
            mousedown: () => {
              selectionchangeMouseTrack = true
            },
            // 鼠標 up 事件
            mouseup: debounce(() => selectionChangeFun(), 100),
            // 選擇事件
            selectionchange:  debounce(() => {
              if (selectionchangeMouseTrack) return
              selectionChangeFun()
            }),
            // 雙擊事件
            dblclick: () => selectionChangeFun(),
            // 右鍵事件
            contextmenu: debounce(() => selectionChangeFun())
          }
          Object.keys(eventHandlers).forEach((event) => {
            document.addEventListener(event, eventHandlers[event])
          })

          5、返回內容

          5.1. 純文本內容


          5.2. html 格式


          6. 完整 JS 代碼

          function debounce (fn, time = 500) {
            let timeout = null; // 創建一個標記用來存放定時器的返回值
            return function () {
              clearTimeout(timeout) // 每當觸發時,把前一個 定時器 clear 掉
              timeout = setTimeout(() => { // 創建一個新的 定時器,并賦值給 timeout
                fn.apply(this, arguments)
              }, time)
            }
          }
          
          let selectionchangeMouseTrack = false
          document.addEventListener('mousedown', (e) => {
            selectionchangeMouseTrack = true
            console.log('mousedown', e)
          })
          document.addEventListener('mouseup', debounce((e) => {
            console.log('mouseup', e)
            selectionChangeFun()
          }, 100))
          document.addEventListener('selectionchange', debounce((e) => {
            console.log('selectionchange', e)
            if (selectionchangeMouseTrack) return
            selectionChangeFun()
          }))
          document.addEventListener('dblclick', (e) => {
            console.log('dblclick', e)
            selectionChangeFun()
          })
          document.addEventListener('contextmenu',debounce(() => {
            selectionChangeFun()
          }))
          
          const selectionChangeFun = debounce(() => {
            const selectContent = getSelectContent()
            selectionchangeMouseTrack = false
            console.log('selectContent', selectContent)
          })
          
          const getSelectContent = () => {
            const selection = window.getSelection();
            if (selection) {
              // 1. 是焦點在 input 輸入框
              // 2. 沒有選中
              // 3. 選擇的是輸入框
              if (selection.isCollapsed) {
                return selection.toString().trim().length ? {
                  type: 'text',
                  content: selection.toString().trim()
                } : null
              }
              // 獲取選擇范圍
              const range = selection.getRangeAt(0);
              // 獲取選擇內容
              const rangeClone = range.cloneContents()
              // 判斷選擇內容里面有沒有節點
              if (rangeClone.childElementCount > 0) {
                const container = document.createElement('div');
                container.appendChild(rangeClone);
                if (!selection.toString().trim().length) {
                  const hasSpNode = getSpNode(container)
                  return hasSpNode ? {
                    type: 'html',
                    content: container.innerHTML
                  } : null
                }
                return {
                  type: 'html',
                  content: container.innerHTML
                }
              } else {
                return selection.toString().trim().length ? {
                  type: 'text',
                  content: selection.toString().trim()
                } : null
              }
            } else {
              return null
            }
          }
          
          const getSpNode = (parent) => {
            const nodeNameList = ['iframe', 'svg', 'img', 'audio', 'video']
            const inpList = ['input', 'textarea', 'select']
            return Array.from(parent.children).some((node) => {
              if (nodeNameList.includes(node.nodeName.toLocaleLowerCase())) return true
              if (inpList.includes(node.nodeName.toLocaleLowerCase()) && node.value.trim().length) return true
              if (node.children) {
                return getSpNode(node)
              }
              return false
            })
          }

          四、總結

          1. 鼠標框選上報能監控用戶在頁面的行為,能為后續的數據分析等提供便利
          2. 基于 JS 中的 Selection 和 Range 實現的,使用原生 JS
          3. 涉及到的操作比較多,包含鍵盤、鼠標右鍵、全選等
          4. 能對框選的內容進行分類,區別 html 和 text,更方便的看出用戶選擇了哪些內容

          次為大家介紹了如果用 Python 抓取公號文章并保存成 PDF 文件存儲到本地。但用這種方式下載的 PDF 只有文字沒有圖片,所以只適用于沒有圖片或圖片不重要的公眾號,那如果我想要圖片和文字下載下來怎么辦?今天就給大家介紹另一種方案——HTML。

          需解決的問題

          其實我們要解決的有兩個問題:

          1. 公眾號里的圖片沒有保存到 PDF 文件里。
          2. 公眾號里的一些代碼片段,尤其那些單行代碼比較長的,保存成 PDF 會出現代碼不全的問題。
          3. PDF 會自動分頁,如果是代碼或圖片就會出現一些問題。

          綜上問題,我覺得還是把公眾號下載成網頁 HTML 格式最好看,下面就介紹下如何實現。

          功能實現

          獲取文章鏈接的方式,和上一篇下載成 PDF 的文章一樣,依然是通過公眾號平臺的圖文素材里超鏈接查詢實現,在這里我們直接拿來上一期的代碼,進行修改即可。首先將原來文件 gzh_download.py 復制成 gzh_download_html.py,然后在此基礎進行代碼改造:

          # gzh_download_html.py
          # 引入模塊
          import requests
          import json
          import re
          import time
          from bs4 import BeautifulSoup
          import os
          
          # 打開 cookie.txt
          with open("cookie.txt", "r") as file:
              cookie = file.read()
          cookies = json.loads(cookie)
          url = "https://mp.weixin.qq.com"
          #請求公號平臺
          response = requests.get(url, cookies=cookies)
          # 從url中獲取token
          token = re.findall(r'token=(\d+)', str(response.url))[0]
          # 設置請求訪問頭信息
          headers = {
              "Referer": "https://mp.weixin.qq.com/cgi-bin/appmsg?t=media/appmsg_edit_v2&action=edit&isNew=1&type=10&token=" + token + "&lang=zh_CN",
              "Host": "mp.weixin.qq.com",
              "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36",
          }
          
          # 循環遍歷前10頁的文章
          for j in range(1, 10, 1):
              begin = (j-1)*5
              # 請求當前頁獲取文章列表
              requestUrl = "https://mp.weixin.qq.com/cgi-bin/appmsg?action=list_ex&begin="+str(begin)+"&count=5&fakeid=MzU1NDk2MzQyNg==&type=9&query=&token=" + token + "&lang=zh_CN&f=json&ajax=1"
              search_response = requests.get(requestUrl, cookies=cookies, headers=headers)
              # 獲取到返回列表 Json 信息
              re_text = search_response.json()
              list = re_text.get("app_msg_list")
              # 遍歷當前頁的文章列表
              for i in list:
                  # 目錄名為標題名,目錄下存放 html 和圖片
                  dir_name = i["title"].replace(' ','')
                  print("正在下載文章:" + dir_name)
                  # 請求文章的 url ,獲取文章內容
                  response = requests.get(i["link"], cookies=cookies, headers=headers)
                  # 保存文章到本地
                  save(response, dir_name, i["aid"])
                  print(dir_name + "下載完成!")
              # 過快請求可能會被微信問候,這里進行10秒等待
              time.sleep(10)

          好了,從上面代碼可以看出,主要就是將原來的方法 pdfkit.from_url(i["link"], i["title"] + ".pdf") 改成了現在的方式,需要用 requests 請求下文章的 URL ,然后再調用保存文章頁面和圖片到本地的方法,這里的 save() 方法通過以下代碼實現。

          調用保存方法

          #保存下載的 html 頁面和圖片
          def save(search_response,html_dir,file_name):
              # 保存 html 的位置
              htmlDir = os.path.join(os.path.dirname(os.path.abspath(__file__)), html_dir)
              # 保存圖片的位置
              targetDir = os.path.join(os.path.dirname(os.path.abspath(__file__)),html_dir + '/images')
              # 不存在創建文件夾
              if not os.path.isdir(targetDir):
                  os.makedirs(targetDir)
              domain = 'https://mp.weixin.qq.com/s'
              # 調用保存 html 方法
              save_html(search_response, htmlDir, file_name)
              # 調用保存圖片方法
              save_file_to_local(htmlDir, targetDir, search_response, domain)
          
          # 保存圖片到本地
          def save_file_to_local(htmlDir,targetDir,search_response,domain):
              # 使用lxml解析請求返回的頁面
              obj = BeautifulSoup(save_html(search_response,htmlDir,file_name).content, 'lxml')  
              # 找到有 img 標簽的內容
              imgs = obj.find_all('img')
              # 將頁面上圖片的鏈接加入list
              urls = []
              for img in imgs:
                  if 'data-src' in str(img):
                      urls.append(img['data-src'])
                  elif 'src=""' in str(img):
                      pass
                  elif "src" not in str(img):
                      pass
                  else:
                      urls.append(img['src'])
          
              # 遍歷所有圖片鏈接,將圖片保存到本地指定文件夾,圖片名字用0,1,2...
              i = 0
              for each_url in urls:
                  # 跟據文章的圖片格式進行處理
                  if each_url.startswith('//'):
                      new_url = 'https:' + each_url
                      r_pic = requests.get(new_url)
                  elif each_url.startswith('/') and each_url.endswith('gif'):
                      new_url = domain + each_url
                      r_pic = requests.get(new_url)
                  elif each_url.endswith('png') or each_url.endswith('jpg') or each_url.endswith('gif') or each_url.endswith('jpeg'):
                      r_pic = requests.get(each_url)
                  # 創建指定目錄
                  t = os.path.join(targetDir, str(i) + '.jpeg')
                  print('該文章共需處理' + str(len(urls)) + '張圖片,正在處理第' + str(i + 1) + '張……')
                  # 指定絕對路徑
                  fw = open(t, 'wb')
                  # 保存圖片到本地指定目錄
                  fw.write(r_pic.content)
                  i += 1
                  # 將舊的鏈接或相對鏈接修改為直接訪問本地圖片
                  update_file(each_url, t, htmlDir)
                  fw.close()
          
              # 保存 HTML 到本地
              def save_html(url_content,htmlDir,file_name):
                  f = open(htmlDir+"/"+file_name+'.html', 'wb')
                  # 寫入文件
                  f.write(url_content.content)
                  f.close()
                  return url_content
          
              # 修改 HTML 文件,將圖片的路徑改為本地的路徑
              def update_file(old, new,htmlDir):
                   # 打開兩個文件,原始文件用來讀,另一個文件將修改的內容寫入
                  with open(htmlDir+"/"+file_name+'.html', encoding='utf-8') as f, open(htmlDir+"/"+file_name+'_bak.html', 'w', encoding='utf-8') as fw:
                      # 遍歷每行,用replace()方法替換路徑
                      for line in f:
                          new_line = line.replace(old, new)
                          new_line = new_line.replace("data-src", "src")
                           # 寫入新文件
                          fw.write(new_line)
                  # 執行完,刪除原始文件
                  os.remove(htmlDir+"/"+file_name+'.html')
                  time.sleep(5)
                  # 修改新文件名為 html
                  os.rename(htmlDir+"/"+file_name+'_bak.html', htmlDir+"/"+file_name+'.html')

          好了,上面就是將文章頁面和圖片下載到本地的代碼,接下來我們運行命令 python gzh_download_html.py ,程序開始執行,打印日志如下:

          $ python gzh_download_html.py
          正在下載文章:學習Python看這一篇就夠了!
          該文章共需處理3張圖片,正在處理第1張……
          該文章共需處理3張圖片,正在處理第2張……
          該文章共需處理3張圖片,正在處理第3張……
          學習Python看這一篇就夠了!下載完成!
          正在下載文章:PythonFlask數據可視化
          該文章共需處理2張圖片,正在處理第1張……
          該文章共需處理2張圖片,正在處理第2張……
          PythonFlask數據可視化下載完成!
          正在下載文章:教你用Python下載手機小視頻
          該文章共需處理11張圖片,正在處理第1張……
          該文章共需處理11張圖片,正在處理第2張……
          該文章共需處理11張圖片,正在處理第3張……
          該文章共需處理11張圖片,正在處理第4張……
          該文章共需處理11張圖片,正在處理第5張……
          該文章共需處理11張圖片,正在處理第6張……
          該文章共需處理11張圖片,正在處理第7張……

          現在我們去程序存放的目錄,就能看到以下都是以文章名稱命名的文件夾:

          進入相應文章目錄,可以看到一個 html 文件和一個名為 images 的圖片目錄,我們雙擊打開擴展名為 html 的文件,就能看到帶圖片和代碼框的文章,和在公眾號看到的一樣。

          總結

          本文為大家介紹了如何通過 Python 將公號文章批量下載到本地,并保存為 HTML 和圖片,這樣就能實現文章的離線瀏覽了。當然如果你想將 HTML 轉成 PDF 也很簡單,直接用 pdfkit.from_file(xx.html,target.pdf) 方法直接將網頁轉成 PDF,而且這樣轉成的 PDF 也是帶圖片的。


          主站蜘蛛池模板: 毛片一区二区三区无码| 亚洲乱码国产一区三区| 日韩精品无码视频一区二区蜜桃 | 国产福利电影一区二区三区,日韩伦理电影在线福| 国产成人免费一区二区三区| 麻豆aⅴ精品无码一区二区| 八戒久久精品一区二区三区| 亚洲AV成人一区二区三区在线看 | 一区二区三区影院| 精彩视频一区二区| 波多野结衣AV一区二区三区中文 | 久久一区二区三区精品| 久久久不卡国产精品一区二区| 毛片一区二区三区无码| 国产精品高清一区二区人妖| 亚洲av无码一区二区三区网站 | 无码国产精品一区二区免费| 亚洲国产精品一区| 少妇无码一区二区三区免费| 国产成人精品视频一区二区不卡| 在线观看国产区亚洲一区成人| 亚洲AV无码一区二区三区国产 | 老熟妇仑乱一区二区视頻| 亚洲AV综合色区无码一区爱AV| 久久精品一区二区| 男女久久久国产一区二区三区| 国产一区二区电影| 麻豆一区二区在我观看| 消息称老熟妇乱视频一区二区| 亚洲视频一区二区| 中文字幕精品一区二区精品| 中文字幕亚洲一区| 色窝窝无码一区二区三区| 亚洲综合无码一区二区| 无码精品尤物一区二区三区| 精品日韩一区二区三区视频| 国产精品亚洲专区一区| 夜夜精品无码一区二区三区| 成人免费区一区二区三区| 久久久国产精品一区二区18禁| 无码人妻精品一区二区三区99性|