yannxtrem / grsu-timetable
PHP scraper for student group timetables from Yanka Kupala State University of Grodno (GRSU) - raspisanie.grsu.by
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
- symfony/css-selector: ^6.0|^7.0
- symfony/dom-crawler: ^6.0|^7.0
Requires (Dev)
- phpunit/phpunit: ^10.0|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A PHP scraper library for fetching and parsing student group class schedules from the Yanka Kupala State University of Grodno (GRSU) online timetable at raspisanie.grsu.by.
Features
- Scrapes weekly schedules from the GRSU timetable portal
- Parses weekday, date, time, discipline, lecturer, subgroup, and classroom data
- Built-in in-memory cache with configurable TTL
- Drop-in Laravel cache adapter via service provider
toArray()output format compatible with the existing Python/Flask API- Supports both plain PHP and Laravel integration
Requirements
- PHP ^8.1
- GuzzleHttp ^7.0
- Symfony DomCrawler + CSS Selector ^6.0 | ^7.0
Installation
composer require yannxtrem/grsu-timetable
Laravel auto-discovery registers the service provider automatically.
Quick Start
Plain PHP
use Yannxtrem\GrsuTimetable\GrsuTimetable; $timetable = new GrsuTimetable(); // Fetch schedule for a specific date $week = $timetable->forDate('2026-04-22'); if ($week !== null) { echo "Group: " . $week->groupName . "\n"; echo "Week: " . $week->weekNumber . "\n"; foreach ($week->days as $day) { echo "Day: " . $day->weekday . " / " . $day->monthday . "\n"; foreach ($day->classes as $class) { echo " " . $class->time->start->format('H:i') . " - " . $class->time->end->format('H:i'); echo " " . $class->subject->name; echo " (" . $class->subgroup . ")"; if ($class->door !== null) { echo " Room: " . $class->door; } echo "\n"; } } }
Laravel
The package auto-registers via extra.laravel.providers in composer.json. You can publish the config:
php artisan vendor:publish --tag=grsu-timetable-config
Resolve via dependency injection or the alias:
use Yannxtrem\GrsuTimetable\GrsuTimetable; // Via DI $week = app(GrsuTimetable::class)->forDate(now()); // Via alias $week = app('grsu-timetable')->forDate(now(), ['arg0' => '12345']);
Usage
forDate()
Fetches the weekly schedule containing the given date. This is the primary entry point.
$week = $timetable->forDate('2026-04-22'); // With a DateTime object $week = $timetable->forDate(new DateTimeImmutable('2026-04-22')); // Override group parameters $week = $timetable->forDate('2026-04-22', [ 'arg0' => '12345', 'arg1' => '3', 'arg2' => '2', 'arg3' => '1', 'arg4' => '1', ]); // Force refresh (bypass cache) $week = $timetable->forDate('2026-04-22', forceRefresh: true); // Skip scraping if not cached $week = $timetable->forDate('2026-04-22', scrapeIfMissing: false);
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
$date |
DateTimeInterface|string |
required | Date in Y-m-d format or a DateTime object |
$groupParams |
array |
[] |
Override for arg0-arg4 group parameters |
$forceRefresh |
bool |
false |
Bypass cache and re-scrape |
$scrapeIfMissing |
bool |
true |
Auto-scrape when not found in cache |
Returns: ?ScheduleWeek
Throws: InvalidArgumentException if the date string is malformed.
getByGroup()
Retrieves a schedule by group name and week number, using cache-first lookup.
$week = $timetable->getByGroup('СДП-УИР-251', 2); // With a known URL for scraping on cache miss $week = $timetable->getByGroup('СДП-УИР-251', 2, url: 'https://raspisanie.grsu.by/...');
Returns null if not cached and no URL is provided.
scrapeUrl()
Scrapes a raw URL directly, bypassing cache read. The result is always stored in cache.
$week = $timetable->scrapeUrl('https://raspisanie.grsu.by/TimeTable/PrintPage.aspx?arg0=18156&...');
Cache Management
// List all cached URLs $urls = $timetable->cachedUrls(); // Clear all managed cache entries $timetable->clearCache(); // Swap the cache store at runtime use Yannxtrem\GrsuTimetable\Cache\LaravelCacheStore; $timetable->setCache(new LaravelCacheStore($laravelCache));
Configuration
Publish the config file in Laravel:
php artisan vendor:publish --tag=grsu-timetable-config
This creates config/grsu-timetable.php:
return [ // Default group identifiers for raspisanie.grsu.by 'default_group_params' => [ 'arg0' => '18156', // Main group ID 'arg1' => '3', 'arg2' => '2', // Week number 'arg3' => '1', 'arg4' => '1', ], // Cache TTL in seconds (default: 1 hour) 'cache_ttl' => 3600, 'cache' => [ 'enabled' => true, // false = use the package's in-memory cache 'store' => null, // null = Laravel's default cache store ], ];
Data Models
All models implement toArray() for serialization and fromArray() for deserialization.
ScheduleWeek
| Property | Type | Description |
|---|---|---|
$weekNumber |
int |
Week number on the timetable |
$groupName |
string |
Student group name |
$days |
ScheduleDay[] |
Array of days in the week |
ScheduleDay
| Property | Type | Description |
|---|---|---|
$weekday |
int |
Day of week (0 = Monday, 6 = Sunday) |
$monthday |
int |
Day of month |
$classes |
ScheduleClass[] |
Classes scheduled for this day |
ScheduleClass
| Property | Type | Description |
|---|---|---|
$subject |
Subject |
The discipline being taught |
$time |
ScheduleTime |
Start and end times |
$subgroup |
string |
Subgroup name ("Tous" if none) |
$door |
?string |
Classroom/auditorium (null if not specified) |
ScheduleTime
| Property | Type | Description |
|---|---|---|
$start |
DateTimeImmutable |
Class start time |
$end |
DateTimeImmutable |
Class end time |
Subject
| Property | Type | Description |
|---|---|---|
$name |
string |
Discipline name |
$lecturer |
Lecturer |
Assigned lecturer |
Lecturer
| Property | Type | Description |
|---|---|---|
$name |
string |
Lecturer name |
Array Output Format
The toArray() method produces a structure matching the existing Python/Flask API:
{
"week_number": 2,
"group_name": "СДП-УИР-251",
"days": [
{
"weekday": 0,
"monthday": 20,
"classes": [
{
"subject": {
"name": "Математический анализ",
"lecturer": {
"name": "Иванов И.И."
}
},
"time": {
"start": "09:40",
"end": "11:05"
},
"subgroup": "1",
"door": "ауд. 212"
}
]
}
]
}
Cache
The package ships with two cache implementations:
| Store | Description |
|---|---|
ArrayCacheStore |
In-memory cache with TTL (default) |
LaravelCacheStore |
Wraps Laravel's Cache::store() |
In Laravel, the service provider automatically uses LaravelCacheStore when cache.enabled is true in the config.
Testing
vendor/bin/phpunit
Tests use Guzzle MockHandler against a local HTML fixture -- no network requests are made.
License
MIT