-
Notifications
You must be signed in to change notification settings - Fork 5
Improve scrapers content extraction #19
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
8ebe583
Completed scraping, meets all requirements
mikesiez b61e058
better nav identification to be removed
mikesiez 69b7f5b
Added bs4, fixed scraper, added offline testing
b6feb41
linter fixes
89af866
more linter fixes:
6394893
linter fixes fixes
0077be6
Various adjustments made. [Checkpoint]
eeb2107
fixed all issues
84e2268
Merge branch 'main' into improve-scrapers-content-extraction
AJaccP f0ce572
remove stray output file
AJaccP c7acab5
remove cut marker and lint
AJaccP 4abc154
add link marker test and lint
AJaccP fd4f191
eol fix
AJaccP File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,43 +1,103 @@ | ||
| import httpx | ||
| import trafilatura | ||
|
|
||
| from src.config.logger import get_logger | ||
|
|
||
| log = get_logger(__name__) | ||
|
|
||
|
|
||
| def scrape(url: str) -> tuple[str, str | None]: | ||
| """Fetch a URL and extract clean text + title. | ||
|
|
||
| Returns (text, title). Title may be None if not found. Text may be empty | ||
| if trafilatura couldn't extract anything; the caller should check and skip. | ||
| """ | ||
| log.info("scrape_started", url=url) | ||
| with httpx.Client(timeout=30, follow_redirects=True) as client: | ||
| response = client.get(url) | ||
| response.raise_for_status() | ||
|
|
||
| # Known limitations (tracked as a separate ticket): | ||
| # - Misses content inside aria-hidden="true" accordions (common on FAQ pages) | ||
| # - Misses question text but gets answer text on other FAQ pages, we should get both | ||
| # - Link formatting is markdown-style and may need cleanup downstream | ||
| # Improving extraction quality is a tuning ticket, not a blocker for development. | ||
| text = ( | ||
| trafilatura.extract( | ||
| response.text, | ||
| favor_recall=True, | ||
| include_links=True, | ||
| include_tables=True, | ||
| include_formatting=False, | ||
| include_comments=False, | ||
| deduplicate=True, | ||
| output_format="txt", | ||
| ) | ||
| or "" | ||
| ) | ||
|
|
||
| metadata = trafilatura.extract_metadata(response.text) | ||
| title = metadata.title if metadata else None | ||
|
|
||
| log.info("scrape_complete", url=url, chars=len(text), title=title) | ||
| return text, title | ||
| import re | ||
| from urllib.parse import urlsplit | ||
|
|
||
| import httpx | ||
| from bs4 import BeautifulSoup | ||
|
|
||
| from src.config.logger import get_logger | ||
|
|
||
| log = get_logger(__name__) | ||
|
|
||
| _BOILERPLATE_TAGS = ["header", "footer", "nav", "script", "style", "noscript"] | ||
| _BOILERPLATE_SELECTORS = ( | ||
| ".navbar-container, .navbar, .footer, .global-nav, " | ||
| ".navigation, .topbar, .content__meta, .visuallyhidden, " | ||
| ".resource__contributors-list, #on-this-page, .resource__sources__heading" | ||
| ) | ||
| _BLOCK_TAGS = [ | ||
| "p", | ||
| "div", | ||
| "li", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "tr", | ||
| "section", | ||
| "article", | ||
| "ul", | ||
| "ol", | ||
| "table", | ||
| "br", | ||
| "blockquote", | ||
| ] | ||
|
|
||
|
|
||
| def _extract_from_html(html: str) -> tuple[str, str | None]: | ||
| log.info("html_extract_start", html_len=len(html)) | ||
|
|
||
| soup = BeautifulSoup(html, "html.parser") | ||
|
|
||
| root = soup.select_one("main, [role=main], #main-content") | ||
| if root is None: # CCSS-style pages have no <main> | ||
| root = soup.body or soup | ||
|
|
||
| # strip boilerplate from within the extracted region | ||
| for t in root(_BOILERPLATE_TAGS): | ||
| t.decompose() | ||
| for el in root.select(_BOILERPLATE_SELECTORS): | ||
| el.decompose() | ||
|
|
||
| log.info("boilerplate_removed") | ||
|
|
||
| # format links after removing boilerplate | ||
| for link in root.find_all("a"): | ||
| href = (link.get("href") or "").strip() | ||
| if href and not href.startswith("#"): | ||
| text = link.get_text(" ", strip=True) | ||
| is_pdf = urlsplit(href).path.lower().endswith(".pdf") | ||
| marker = "PDF" if is_pdf else "Link" | ||
| link.replace_with(f"{text} [{marker}: {href}] ") | ||
|
|
||
| log.info("links_parsed") | ||
|
|
||
| for tag in root.find_all(_BLOCK_TAGS): | ||
| tag.append("\n") | ||
|
|
||
| text = root.get_text(" ", strip=False) | ||
| text = re.sub(r"[ \t]+", " ", text) | ||
| text = re.sub(r" *\n *", "\n", text) | ||
| text = re.sub(r"\n{3,}", "\n\n", text) | ||
| text = text.strip() | ||
|
|
||
| title = soup.title.get_text(strip=True) if soup.title else None | ||
|
|
||
| log.info("html_extract_complete", text_len=len(text), has_title=title is not None) | ||
| return text, title | ||
|
|
||
|
|
||
| def scrape(url: str) -> tuple[str, str | None]: | ||
| """Fetch a URL and extract clean text + title. | ||
|
|
||
| Returns (text, title). Title may be None if not found. | ||
| """ | ||
| log.info("scrape_called", url=url) | ||
|
|
||
| log.info("http_request_start", url=url) | ||
| with httpx.Client(timeout=30, follow_redirects=True) as client: | ||
| response = client.get(url) | ||
| response.raise_for_status() | ||
|
|
||
| log.info( | ||
| "http_request_success", | ||
| url=url, | ||
| status_code=response.status_code, | ||
| bytes=len(response.text), | ||
| ) | ||
|
|
||
| log.info("html_parsing_start", url=url) | ||
| text, title = _extract_from_html(response.text) | ||
| log.info("scrape_complete", url=url, chars=len(text), title=title) | ||
| return text, title | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| <!doctype html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><link rel=stylesheet href=https://ccss.carleton.ca/css/styles.6e91bbb8770e829f7718dc3011c02037c6c550a9338b13c8b1f092ca356325adabdb3a1ded197ef6fa835569b88930d412d5581f7c630e13a1d0c39679202e43.css crossorigin=anonymous integrity="sha512-bpG7uHcOgp93GNwwEcAgN8bFUKkzixPIsfCSyjVjJa2r2zod7Rl+9vqDVWm4iTDUEtVYH3xjDhOh0MOWeSAuQw=="><link rel=stylesheet href=https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin=anonymous referrerpolicy=no-referrer><link rel=preconnect href=https://fonts.gstatic.com><link href="https://fonts.googleapis.com/css2?family=Nunito:wght@600&display=swap" rel=stylesheet><link href=https://ccss.carleton.ca/favicon.ico rel=icon /><title>Carleton Computer Science Society | Making a Balanced Course Schedule</title> | ||
| <meta name=description content="The Carleton Computer Science Society is a student led organization that represents all computer science students attending Carleton University."><meta property="og:type" content="website"><meta property="og:url" content="https://ccss.carleton.ca"><meta property="og:title" content="Carleton Computer Science Society | Making a Balanced Course Schedule"><meta property="og:description" content="The Carleton Computer Science Society is a student led organization that represents all computer science students attending Carleton University."><meta property="og:image" content="https://ccss.carleton.ca/images/orientation2018-min.jpeg"><meta property="twitter:card" content="summary_large_image"><meta property="twitter:url" content="https://ccss.carleton.ca"><meta property="twitter:title" content="Carleton Computer Science Society | Making a Balanced Course Schedule"><meta property="twitter:description" content="The Carleton Computer Science Society is a student led organization that represents all computer science students attending Carleton University."><meta property="twitter:image" content="https://ccss.carleton.ca/images/orientation2018-min.jpeg"><meta http-equiv=Cache-control content="no-cache"><link rel=apple-touch-icon href=images/ccss-logo-ios.png></head><body><div class=navbar-container><div class=navbar-wrapper><div class=navbar id=primary_nav_wrap><a href=https://ccss.carleton.ca/ class="nav-brand nav_item"><img src=/images/ccss-logo-2022.png class=navbar-logo alt="The logo of the Carleton Computer Science Society"></a><ul class=navbar-items><li><a href=/about>ABOUT</a><ul><li><a href=/about/team>THE TEAM</a></li><li><a href=/about/governance>GOVERNANCE</a></li><li><a href=/partnerships>PARTNER WITH US</a></li><li><a href=https://docs.google.com/spreadsheets/d/171SP2PWsn6LUza4QzX0nNi2-qwFZnKN-2Q8ERwGkV6o target=_blank>BUDGET</a></li></ul></li><li><a href=/events>EVENTS</a><ul><li><a href=/jobuary>JOBUARY</a></li><li><a href=/hackthetunnels>HACK THE TUNNELS</a></li><li><a href=/events/weekofawesome>WEEK OF AWESOME</a></li></ul></li><li><a href=/news>NEWS</a></li><li><a href=#>COMMUNITY</a><ul><li><a href=/blog>BLOG</a></li><li><a href=/community/organizations>ORGANIZATIONS</a></li></ul></li><li><a href=/volunteer>VOLUNTEER</a></li><li><a href=/resources/>RESOURCES</a></li></ul><div class=mobile-hamburger><i class="fas fa-bars"></i></div></div></div></div><header class=header><a href=https://ccss.carleton.ca/ class="nav-brand nav_item"><img width=180px src=/images/ccss-logo-2022.png alt="The logo of the Carleton Computer Science Society"> | ||
| </a><input class=menu-btn type=checkbox id=menu-btn> | ||
| <label class=menu-icon for=menu-btn><span class=navicon></span></label><ul class=menu><li class=mobile-dropdown><div class=mobile-dropdown-header><a href=/about>ABOUT</a> | ||
| <button class=mobile-dropdown-toggle aria-label="Toggle submenu"> | ||
| <i class="fa fa-chevron-down"></i></button></div><ul class=mobile-submenu><li><a href=/about/team>THE TEAM</a></li><li><a href=/about/governance>GOVERNANCE</a></li><li><a href=/partnerships>PARTNER WITH US</a></li><li><a href=https://docs.google.com/spreadsheets/d/171SP2PWsn6LUza4QzX0nNi2-qwFZnKN-2Q8ERwGkV6o target=_blank>BUDGET</a></li></ul></li><li class=mobile-dropdown><div class=mobile-dropdown-header><a href=/events>EVENTS</a> | ||
| <button class=mobile-dropdown-toggle aria-label="Toggle submenu"> | ||
| <i class="fa fa-chevron-down"></i></button></div><ul class=mobile-submenu><li><a href=/jobuary>JOBUARY</a></li><li><a href=/hackthetunnels>HACK THE TUNNELS</a></li><li><a href=/events/weekofawesome>WEEK OF AWESOME</a></li></ul></li><li><a href=/news>NEWS</a></li><li class=mobile-dropdown><div class=mobile-dropdown-header><a href=#>COMMUNITY</a> | ||
| <button class=mobile-dropdown-toggle aria-label="Toggle submenu"> | ||
| <i class="fa fa-chevron-down"></i></button></div><ul class=mobile-submenu><li><a href=/blog>BLOG</a></li><li><a href=/community/organizations>ORGANIZATIONS</a></li></ul></li><li><a href=/volunteer>VOLUNTEER</a></li><li><a href=/resources/>RESOURCES</a></li></ul></header><script>document.querySelectorAll(".mobile-dropdown-toggle").forEach(function(e){e.addEventListener("click",function(){e.closest(".mobile-dropdown").classList.toggle("open")})})</script><div class=article-question__page><div class=article-question__content><h1 class=article-question__title>Making a Balanced Course Schedule</h1><div class=article-question__summary><i>Careful planning can help you create a timetable that supports your academic goals, fits your lifestyle, and keeps your workload manageable.</i></div><div class=resource__updated-at><i class="far fa-clock" aria-hidden=true></i> | ||
| Updated: | ||
| June 27, 2026</div><hr class=resource__separator style=margin-bottom:-.7rem><div class=article-question__body><h3 id=consider-all-the-possibilities>Consider all the Possibilities</h3><p>When you are initially building your schedule, you might find it helpful to <strong>start by adding in all possible sections</strong> of your mandatory classes, and do the same for any elective classes you absolutely want to take.</p><p>While this may look a little scary at first, you will have a visual representation of classes that may cause timing conflicts in your schedule - which makes planning your classes a bit easier.</p><p>If you are going into your first year, classes that you will (likely) need to add are:</p><ul><li>COMP 1405</li><li>COMP 1406</li><li>COMP 1805</li><li>MATH 1007</li><li>MATH 1104</li></ul><p>You can visit <a href=https://carleton.ca/registration/course-selection-guide/bcs/>this</a> SCS page to view suggested course sequences for first-year students, if you’re curious about when students typically take the courses mentioned above.</p><p><strong>NOTE:</strong> <em>The courses listed above are for <strong>Bachelor of Computer Science</strong> students. If you’re a <strong>Bachelor of Cybersecurity</strong> student, refer to your <a href="https://carleton.ca/scs/current-students/bachelor-of-cybersecurity/bcyber-courses-and-registration/#:~:text=a%20payment.-,Course%20Patterns,-It%20is%20recommended">course pattern</a> instead.</em></p><br><h3 id=class-timing>Class Timing</h3><p>An important thing to consider when planning your schedule is class timing, as it can impact your class attendance, or how successful you are in the course.</p><p><strong>Are you a morning person?</strong> If yes, maybe you want to consider taking early morning classes to free up the rest of your day.</p><p><strong>Are you a night owl who wakes up late?</strong> If this is the case, maybe willingly adding an 8:30 am class into your scheduling is a bad idea.</p><p>Do you <strong>have obligations during the evening, or just like hanging out at night?</strong> If so, you might want to consider taking classes earlier in the day, or taking “unscheduled” courses that are flexible as to when classwork needs to be completed.</p><p>If you’re a <strong>commuter student</strong> - you might want to consider trying to <strong>limit your in-person classes to certain days</strong>, in order to save you some travel time.</p><br><h3 id=lunch-time>Lunch Time</h3><p>When adding classes, you may be tempted to add all of your classes consecutively to consolidate your classes to two or three days of your schedule.</p><p>While you might think you’ll be okay - consider planning a gap in your schedule in order to <strong>have time to eat your lunch.</strong> It likely won’t be possible to align it to a certain time frame, but having time to refuel in order to be able to focus in class is critical!</p><br><h3 id=other-factors-outside-of-timing-to-consider>Other Factors (Outside of Timing) to Consider</h3><p>Outside of class timing, there are a couple of other factors that you should take into consideration when planning your classes.</p><p>Here are some guiding questions:</p><ul><li>What kind of course is it? Is it a mandatory class? Is it a prerequisite for other classes?</li><li>Is it online-friendly? Are there live lectures or is it unscheduled?</li><li>Do you need to sprint across campus between blocks?</li></ul><br><h3 id=time-ticket-madness>Time-Ticket Madness</h3><p>If you are not one of the lucky few to have an early registration <a href=https://carleton.ca/registration/dates/timetickets/>time ticket</a>, the initial schedule that you made may not work out, as <strong>some classes may fill up before your time ticket.</strong> In order to prevent this being an issue for you - you can do a couple of things to prepare.</p><p>First off, before your time ticket, you can <strong>create multiple different versions of your schedule</strong> with different sections of the same classes. However, this isn’t fail-proof, and one of the classes that you were interested in taking might be entirely full. Since there is always a probability of this happening, be prepared to revise your schedule right before your timeslot, in order to not have to rush to adjust courses when it opens.</p><p>If there is a course that you have your heart set on, but it fills up before you’re able to register - you might be able to join a <a href=https://carleton.ca/registration/waitlisting/>waitlist</a> for the course. Oftentimes, a couple of students will drop any given course, meaning that there is a chance that you’d be able to register in it closer to/during the term.</p><br><h3 id=rate-my-prof>Rate My Prof</h3><p>While you may be tempted to solely base your professor choices on their RateMyProf ratings - it is best to <strong>take them with a grain of salt.</strong> However, if most of the reviews are positive - then the rating is more likely to be credible, as negative reviews are sometimes written in reaction to getting a bad grade in the class (which can be a result of lack of engagement in the course on the student’s part).</p><p>If you are seeking to get insight on which professor you might want to choose, you can <strong>ask upper year students for advice!</strong></p><p>You can also check out <a href=https://outline.scs.carleton.ca/>past course outlines</a> to see how specific instructors have strucutured their course in the past.</p><p><strong>NOTE:</strong> <em>Course outlines can change each year. Use past outlines as a reference only, and don’t rely on them as a guarantee of what the course will be like this year.</em></p><br><h3 id=no-schedule-is-perfect>No Schedule is Perfect</h3><p>Even if your schedule doesn’t turn out perfectly - whether due to factors beyond your control or decisions you later wish you’d made differently - it’s not the end of the world. Take note of what didn’t work and use that insight to improve your planning the next time course registration comes around.</p><p>If you ever want to make changes to your courses down the line, <strong>you are able to drop and add courses until the respective deadlines</strong>, which you can find in the <a href=https://students.carleton.ca/academic-dates/>Academic Calendar</a>.</p></div><hr class=resource__separator><div style=height:.5rem></div><div class=resource__contributors><span><i class="far fa-user" aria-hidden=true></i></span> | ||
| <span class=resource__contributors-list><span class=resource__contributor-pill>Veronica Mordvinova</span></span></div><div style=height:.2rem></div><div class=article-question__related-content><h3>Related Content:</h3><div class=resources-topic__questions><a class=resources-topic__question href=/resources/faqs/first-year-courses-order/><div class=faq-content><h3>Do I have to take first year courses in a specific order?</h3><p>COMP 1405 and 1406 should be taken in Fall and Winter respectively. Other first-year core courses can be taken in either term.</p></div><i class="fas fa-arrow-right faq-arrow"></i> | ||
| </a><a class=resources-topic__question href=/resources/articles/reducing-second-year-workload/><div class=faq-content><h3>Ways to Reduce Your Second Year Workload</h3><p>Strategically selecting courses in your first year can help reduce your second-year workload and set you up for academic success.</p></div><i class="fas fa-arrow-right faq-arrow"></i></a></div></div><div style=height:.2rem></div><div class=resource__sources><div><i class="fas fa-external-link-alt" aria-hidden=true></i> | ||
| <span class=resource__sources__heading>Links and Sources</span></div><p>(1) | ||
| <a href=https://carleton.ca/registration/course-selection-guide/bcs/>BCS first year course selection guide</a></p><p>(2) | ||
| <a href=https://carleton.ca/registration/dates/timetickets/>Time Tickets</a></p><p>(3) | ||
| <a href=https://carleton.ca/registration/waitlisting/>Waitlists</a></p><p>(4) | ||
| <a href=https://outline.scs.carleton.ca/>School of Computer Science old course outlines</a></p><p>(5) | ||
| <a href=https://students.carleton.ca/academic-dates/>Academic Dates and Deadlines</a></p></div></div></div><noscript><img src=https://shynet-mpb4.onrender.com/ingress/7b07c3d5-c87b-4c27-a3c0-6d7c060d4d17/pixel.gif></noscript><script defer src=https://shynet-mpb4.onrender.com/ingress/7b07c3d5-c87b-4c27-a3c0-6d7c060d4d17/script.js></script></body><div class=footer><div class=footer-text><p><b>Carleton Computer Science Society</b><br>4135 Herzberg Laboratories<br>1125 Colonel By Drive,<br>Ottawa, ON K1S 5B6<br><br>email: <a href=mailto:bod@ccss.carleton.ca>bod@ccss.carleton.ca</a></p></div><div class=footer-socials><a href=http://discord.carletoncomputersciencesociety.ca><i class="fab fa-discord"></i></a> | ||
| <a href=https://www.instagram.com/carletoncss/><i class="fab fa-instagram"></i></a> | ||
| <a href=https://www.facebook.com/CarletonComputerScienceSociety><i class="fab fa-facebook"></i></a> | ||
| <a href=https://ca.linkedin.com/company/carleton-computer-science-society><i class="fab fa-linkedin"></i></a> | ||
| <a href=https://www.youtube.com/@carletoncomputersciencesoc305><i class="fab fa-youtube"></i></a> | ||
| <a href=https://github.com/CarletonComputerScienceSociety><i class="fab fa-github"></i></a></div><div class=footer-copyright>© 2026 Carleton Computer Science Society</div></div><script src=https://code.jquery.com/jquery-1.9.1.min.js integrity="sha256-wS9gmOZBqsqWxgIVgA8Y9WcQOa7PgSIX+rPA0VL2rbQ=" crossorigin=anonymous></script><script type=text/javascript src=https://ccss.carleton.ca/js/bundle.a84941d9e5d05ec6b591e8f61e5cd392217520701e85eb735060ee6be204d7c359d4b381f91840b289361e49c7a908afc7ec5a92c6a72b3add687ffa3f0f3ac9.js integrity="sha512-qElB2eXQXsa1kej2HlzTkiF1IHAehetzUGDua+IE18NZ1LOB+RhAsok2HknHqQivx+xaksanKzrdaH/6Pw86yQ==" crossorigin=anonymous></script></html> |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.