参考:
https://foofish.net/python-crawler-html2pdf.html
http://www.cnblogs.com/tuohai666/p/8718107.html
最近做python 2 to 3的工作,想要爬取w3c school的python3教程并转换成pdf方便随时查看。
简单搜了一下,找到参考链接的博客,于是开始step by step跟着走。
本文简单记录步骤和遇到的问题,windows10,python36。
工具准备
python packages
pip install requests pip install beautifulsoup4 pip install pdfkit
安装wkhtmltopdf
当前平台是windows,到官网 https://wkhtmltopdf.org/downloads.html 下载稳定版并将程序执行路径加入到系统环境$PATH变量中。
目标页面分析
浏览器F12打开开发者工具,找到目标元素的locator,需要采集的目标元素是以下三个div
- div.siderbar-content: 章节目录
- div.content-top:章节标题
- div.content-bg:章节内容
爬虫实现
任务拆分
- 从python3页面拿到所有章节的URLs。
- 用requests把目标URL整个内容加载到本地, 用beautifulsoup操作HTML的dom元素提取正文部分,存到本地html文件(当前目录的static子目录下)。
- 用pdfkit htmls文件列表,生成一个pdf文档。
第一步,从https://www.w3cschool.cn/python3/ 页面拿到所有章节的URLs。
base_url='https://www.w3cschool.cn/python3/' def get_url_list(): # Get URLs list for python3 tutorial base_url_for_python3 = base_url + '/python3' response = requests.get(base_url_for_python3) soup = BeautifulSoup(response.content.decode(), 'html.parser') menu_tags = soup.find('div','sidebar-content') urls = [] for link in menu_tags.find_all('a'): url = base_url + link.get('href') urls.append(url) return urls
第二步,用requests把目标URL整个内容加载到本地,用beautifulsoup操作HTML的dom元素提取正文部分,存到本地html文件。
def get_content(url): # Get Page Content for each url and save to html files print('Opening URL',url) chapter_name = url.split('/')[-1] response = requests.get(url) soup = BeautifulSoup(response.content.decode(), 'html.parser') # Get chapter title head = soup.find_all('div','content-top') # Get chapter content content = soup.find_all('div','content-bg') html = str(head).encode() + str(content).encode() with open('static/{}'.format(chapter_name), 'wb') as f: f.write(html)
第三步,用pdfkit htmls文件列表,生成一个pdf文档。
def save_as_pdf(htmls): # Save the html file list content to pdf file options = { 'page-size': 'Letter', 'margin-top': '0.75in', 'margin-right': '0.75in', 'margin-bottom': '0.75in', 'margin-left': '0.75in', 'encoding': "UTF-8", 'custom-header': [ ('Accept-Encoding', 'gzip') ], 'cookie': [ ('cookie-name1', 'cookie-value1'), ('cookie-name2', 'cookie-value2'), ], 'outline-depth': 10, } pdfkit.from_file(htmls,'w3c_python3_tutorial.pdf',options=options)
入口函数
def get_w3c_python3_tutorial(): url_list = get_url_list() for url in url_list: get_content(url) ori_list = os.listdir('./static') file_name_list = [ 'static/' + s for s in ori_list ] save_as_pdf(file_name_list)
问题解决
以上代码碰到了两个小问题。
- pdf文件内容没有按照章节先后顺序排序。
- 部分章节获取的内容不全,如 https://www.w3cschool.cn/python3/python3-basic-syntax.html, 因打开页面后,浏览器执行了 javascript脚本动态生成页面。使用requests包无法操作javascript,满足不了动态页面内容的抓取。
第一个问题的解决方法比较简单粗暴…… 定义一个全局变量,指定章节序号,每处理一个章节,序号+1。然后再以数字序号对文件列表排序,将排序好的list传给save_as_pdf。
base_url = 'https://www.w3cschool.cn' counter = 1 def get_content(url): # Get Page Content for each page and save to html files print('Opening URL',url) chapter_name = url.split('/')[-1] response = requests.get(url) soup = BeautifulSoup(response.content.decode(), 'html.parser') head = soup.find_all('div','content-top') content = soup.find_all('div','content-bg') html = str(head).encode() + str(content).encode() global counter file_name = '%d-%s' % (counter, chapter_name) with open('static/{}'.format(file_name), 'wb') as f: f.write(html) counter = counter + 1
def sorted_aphanumeric(list): # Sort list with numbers convert = lambda text: int(text) if text.isdigit() else text.lower() alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ] return sorted(data, key=alphanum_key)
第二个问题,我们要用到另外的python package - selenium。
pip install selenium
从官网下载稳定版的chromedriver.exe - http://chromedriver.chromium.org/downloads ,并将它放到脚本可访问到的目录,本实验将chromedriver.exe放在了脚本的同目录下。
启动webdriver。
from selenium import webdriver from selenium.webdriver.chrome.options import Options chrome_options = Options() chrome_options.add_argument('--headless') # ‘无头’调用chrome,用户不能看到chrome被启动,最新的chrome browser支持headless模式,可以替代phantomjs chrome_options.add_argument('--ignore-certificate-errors') chrome_driver = os.getcwd() + r'\chromedriver.exe' driver = webdriver.Chrome(chrome_options=chrome_options, executable_path=chrome_driver)
使用selenium webdriver获取url list
def get_url_list(): python3_tutorial_url = base_url + '/python3' driver.get(python3_tutorial_url) sidebar_content = driver.find_element_by_css_selector('div.sidebar-content') soup = BeautifulSoup(sidebar_content.get_attribute('innerHTML'), 'html.parser') urls = [] for link in soup.find_all('a'): url = base_url + link.get('href') urls.append(url) return urls
获取页面内容
def get_content(url): print('Opening URL',url) global counter chapter_name = str(counter) + '-' + url.split('/')[-1] driver.get(url) content_top = driver.find_element_by_css_selector('div.content-top') content_bg = driver.find_element_by_css_selector('div.content-bg') content_value = content_top.get_attribute('innerHTML').encode() + content_bg.get_attribute('innerHTML').encode() with open('dynamic/{}'.format(chapter_name), 'wb') as f: # 这次我们存到dynamic子目录下 :) f.write(content_value) counter= counter+1
效果图如下