Скребьте сайт моего учителя CS, а затем по электронной почте, когда сайт обновлен

Я работал над созданием отдельного финального проекта для моего класса python CS, который ежедневно проверяет веб-сайт моего учителя и определяет, изменил ли он какие-либо веб-страницы на своем веб-сайте с момента последнего запуска программы или нет .

Мне бы очень понравились предложения по улучшению моего кода, тем более, что он работает сейчас! Я добавил некоторые функциональные возможности, так что он запускается через задание cron на облачном сервере и отправляет электронное письмо при изменении страницы!

import requests ## downloads the html
from bs4 import BeautifulSoup ## parses the html
import filecmp ## compares files
import os, sys ## used for renaming files
import difflib ## used to see differences in link files
import smtplib ## used for sending email
from email.mime.multipart import MIMEMultipart ## used for areas of email such as subject, toaddr, fromaddr, etc.
from email.mime.text import MIMEText ## used for areas of email such as body, etc.

root_url = "https://sites.google.com"
index_url = root_url + "/site/csc110winter2015/home"

def get_site_links():
    '''
    Gets links from the website's list items' HTML elements
    '''
    response = requests.get(index_url)
    soup = BeautifulSoup(response.text)
    links =  [a.attrs.get('href') for a in soup.select('li.topLevel a[href^=/site/csc110winter2015/]')]
    return links

def try_read_links_file():
    '''
    Tries to read the links.txt file; if links.txt is found, then rename links.txt to previous_links.txt
    '''
    try:
        os.rename("links.txt", "previous_links.txt")
        write_links_file()
    except (OSError, IOError):
        print("No links.txt file exists; creating one now.")
        write_links_file()
        try_read_links_file()

def write_links_file():
    '''
    Writes the links.txt file from the website's links 
    '''
    links = get_site_links()
    with open("links.txt", mode='wt', encoding='utf-8') as out_file:
        out_file.write('\n'.join(links))

def check_links():  
    '''
    Checks to see if links have changed since the last time the program was run.
    '''
    if filecmp.cmp("links.txt", "previous_links.txt") == True:
        ## If link data hasn't changed, do nothing
        pass
    else:
        ## Checks to see what changes, if any, have been made to the links, and outputs them to the console
        d = difflib.Differ()
        previous_links = open("previous_links.txt").readlines()
        links =  open("links.txt").readlines()
        diff = d.compare(previous_links, links)
        for difference in diff:
            if '- ' in difference:
                print(difference.strip() + "\nWas a removed page from the CSC110 website since the last time checked.\n")
            elif '+ ' in difference:
                print(difference.strip() + "\nWas an added page to the CSC110 website since the last time checked.\n")

def try_read_pages_files():
    '''
    Tries to read the pages .txt files; if pages .txt are found, then rename the pages .txt files to previous_ pages .txt
    '''
    with open("links.txt", mode='r', encoding='utf-8') as pages:
        for page in pages:
            try:
                os.rename(page.replace("/",".") + ".txt", "previous_" + page.replace("/",".") + ".txt")
            except (OSError, IOError):
                print("No pages .txt file exists; creating them now.")
                write_pages_files()
                try_read_pages_files()
                ## Note that the call to write_pages_files() is outside the loop
        write_pages_files()

def write_pages_files():
    '''
    Writes the various page files from the website's links 
    '''
    with open("links.txt") as links:
        for page in links:
            site_page = requests.get(root_url + page.strip())
            soup = BeautifulSoup(site_page.text)
            souped_up =  soup.find_all('div', class_= "sites-attachments-row")
            with open(page.replace("/",".") + ".txt", mode='wt', encoding='utf-8') as out_file:
                out_file.write(str(souped_up))

def check_pages():
    '''
    Checks to see if pages have changed since the last time the program was run.
    '''
    with open("links.txt") as links:
        changed_pages = []
        for page in links:
            page = page.replace("/",".")
            if filecmp.cmp("previous_" + page + ".txt", page + ".txt") == True:
                ## If page data hasn't changed, do nothing
                pass
            else:
                ## If page data has changed, then write the changed page data to a list
                if page == '.site.csc110winter2015.system.app.pages.sitemap.hierarchy':
                    pass
                else:
                    changed_pages.append(root_url + page.replace(".","/").strip())
        return changed_pages

def send_mail():
    server = smtplib.SMTP('smtp.gmail.com', 587)
    ## Say ehlo to my lil' friend!
    server.ehlo()
    ## Start Transport Layer Security for Gmail
    server.starttls()
    server.ehlo()
    if check_pages():
        ## Setting up the email
        server.login("Sending Email", "Password")
        fromaddr = "Sending Email"
        toaddr = "Receiving Email"
        msg = MIMEMultipart()
        msg['From'] = fromaddr
        msg['To'] = toaddr
        msg['Subject'] = "Incoming CSC110 website changes!"
        # Can't return list and concatenate string; implemented here for check_pages()
        changed_pages =  "The following page(s) have been updated:\n\n" + str(check_pages())
        msg.attach(MIMEText(changed_pages, 'plain'))
        text = msg.as_string()
        server.sendmail(fromaddr, toaddr, text)

def main():
    try_read_links_file()
    try_read_pages_files()
    check_links()
    check_pages()
    send_mail()
main()
10 голосов | спросил cody.codes 14 MaramSat, 14 Mar 2015 07:45:43 +03002015-03-14T07:45:43+03:0007 2015, 07:45:43

1 ответ


5

Ошибки

В электронной почте перечислены страницы, содержимое которых изменилось, но не страницы, которые были добавлены или удалены. Дополнения и удаления просто печатаются на sys.stdout.

Файлы, в которых сохранены содержимое страницы, имеют имена файлов формы previous_.site.csc110winter2015.somethingsomething␤.txt. Символ новой строки, предшествующий .txt, является странным.

Если ссылки просто переупорядочены, вы увидите, что это сообщение было удалено и добавлено.

Если try_read_links_file() не удалось создать links.txt (из-за прав доступа к каталогам, например), он будет бесконечно возвращаться.

Неэффективность

Вы вызываете check_pages() до трех раз:

  • Внутри main() без видимых причин
  • Один раз в send_mail() в попытке проверить, были ли обнаружены какие-либо изменения. Как ни странно, эта проверка выполняется после с помощью SMTP-рукопожатия - зачем вообще подключаться к SMTP-серверу, если вам нечего отправлять?
  • Если вы решили отправить почту, вы снова вызываете check_pages(), чтобы включить список измененных страниц в теле сообщения.

Общая критика

Используемая вами техника очень ориентирована на файл . Пять функций, которые вы вызываете из main(), обмениваются данными друг с другом, не передавая параметры и возвращаемые значения, ни глобальными переменными, а через файловую систему ! Этот стиль программирования сильно усложняет код. Каждая функция заканчивается тем, что касается чтения файлов, снятия новых строк (если вы это запомнили), путей поиска и сохранения результатов.

try_read_pages_files() вводится в заблуждение, так как он также записывает файлы. Аналогично, try_read_links_file() имеет побочные эффекты, которых я не ожидал.

Если вы просто хотите определить, изменился ли контент, вам не нужно сохранять содержимое всего веб-сайта. Хватить криптографическую контрольную сумму для каждой страницы будет достаточно. С этим пониманием вы можете суммировать весь сайт в одном файле , по одной строке на странице.

Было бы лучше передать весь исходный URL-адрес программе, а не разбить его на root_url и index_url. Кроме того, добавив значения href в код root_url делает неприятное предположение, что все href s являются абсолютными URL-адресами. Используйте urllib.parse.urljoin() для разрешения URL-адресов.

В send_mail() сначала создайте сообщение, а затем отправьте его. Избегайте чередования двух операций. Вам не нужно многопользовательский MIME, если все, что вы отправляете, представляет собой текстовое сообщение.

В предлагаемом ниже решении рассмотрите main(), чтобы увидеть, как функции должны взаимодействовать друг с другом.

from base64 import b64encode, b64decode
from bs4 import BeautifulSoup
from email.mime.text import MIMEText
from hashlib import sha256
from smtplib import SMTP
from urllib.parse import urljoin
from urllib.request import urlopen

def summarize_site(index_url):
    '''
    Return a dict that maps the URL to the SHA-256 sum of its page contents
    for each link in the index_url.
    '''
    summary = {}
    with urlopen(index_url) as index_req:
        soup = BeautifulSoup(index_req.read())
        links = [urljoin(index_url, a.attrs.get('href'))
                 for a in soup.select('li.topLevel a[href^=/site/csc110winter2015/]')]
        for page in links:
            # Ignore the sitemap page
            if page == '/site/csc110winter2015/system/app/pages/sitemap/hierarchy':
                continue    
            with urlopen(page) as page_req:
                fingerprint = sha256()
                soup = BeautifulSoup(page_req.read())
                for div in soup.find_all('div', class_='sites-attachments-row'):
                    fingerprint.update(div.encode())
                summary[page] = fingerprint.digest()
    return summary

def save_site_summary(filename, summary):
    with open(filename, 'wt', encoding='utf-8') as f:
        for path, fingerprint in summary.items():
            f.write("{} {}\n".format(b64encode(fingerprint).decode(), path))

def load_site_summary(filename):
    summary = {}
    with open(filename, 'rt', encoding='utf-8') as f:
        for line in f:
            fingerprint, path = line.rstrip().split(' ', 1)
            summary[path] = b64decode(fingerprint)
    return summary

def diff(old, new):
    return {
        'added': new.keys() - old.keys(),
        'removed': old.keys() - new.keys(),
        'modified': [page for page in set(new.keys()).intersection(old.keys())
                     if old[page] != new[page]],
    }

def describe_diff(diff):
    desc = []
    for change in ('added', 'removed', 'modified'):
        if not diff[change]:
            continue
        desc.append('The following page(s) have been {}:\n{}'.format(
            change,
            '\n'.join(' ' + path for path in sorted(diff[change]))
        ))
    return '\n\n'.join(desc)

def send_mail(body):
    ## Compose the email
    fromaddr = "Sending Email"
    toaddr = "Receiving Email"
    msg = MIMEText(body, 'plain')
    msg['From'] = fromaddr
    msg['To'] = toaddr
    msg['Subject'] = "Incoming CSC110 website changes!"

    ## Send it
    server = SMTP('smtp.gmail.com', 587)
    server.ehlo()
    server.starttls()
    server.ehlo()
    server.login("Sending Email", "Password")
    server.sendmail(fromaddr, toaddr, msg.as_string())
    server.quit()

def main(index_url, filename):
    summary = summarize_site(index_url)
    try:
        prev_summary = load_site_summary(filename)    
        if prev_summary:
            diff_description = describe_diff(diff(prev_summary, summary))
            if diff_description:
                print(diff_description)
                send_mail(diff_description)
    except FileNotFoundError:
        pass
    save_site_summary(filename, summary)

main(index_url='https://sites.google.com/site/csc110winter2015/home',
     filename='site.txt')
ответил 200_success 18 MarpmWed, 18 Mar 2015 12:44:32 +03002015-03-18T12:44:32+03:0012 2015, 12:44:32

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132