Skip to content

Commit d4cc620

Browse files
committed
Fix scraping logic for all pages
1 parent 121ec94 commit d4cc620

9 files changed

Lines changed: 1330 additions & 197 deletions

File tree

README.md

Lines changed: 97 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,75 @@ pip install 'ratemyprofessors-client[sentiment]'
2121

2222
## Quickstart
2323

24+
**Professor by ID** (data from professor page HTML):
25+
2426
```python
2527
from rmp_client import RMPClient
2628

27-
SCHOOL_ID = 1466 # example: Queen's University ID on RMP
29+
with RMPClient() as client:
30+
professor = client.get_professor("2823076") # legacy ID from URL
31+
print(professor.name, professor.overall_rating, professor.num_ratings, professor.school.name)
32+
```
33+
34+
**School by ID** (data from school page HTML):
35+
36+
```python
37+
with RMPClient() as client:
38+
school = client.get_school("1466")
39+
print(school.name, school.location, school.overall_quality, school.num_ratings)
40+
```
41+
42+
**Search professors or schools** (data from search page HTML):
43+
44+
```python
45+
with RMPClient() as client:
46+
profs = client.search_professors("test")
47+
print(profs.total, profs.has_next_page)
48+
for p in profs.professors[:5]:
49+
print(p.name, p.school.name if p.school else "")
50+
51+
schools = client.search_schools("queens")
52+
for s in schools.schools:
53+
print(s.name, s.location)
54+
```
2855

56+
**Compare two schools** (data from compare page HTML):
57+
58+
```python
2959
with RMPClient() as client:
30-
for prof in client.iter_professors_for_school(SCHOOL_ID, page_size=20):
31-
print(prof.name, prof.overall_rating, prof.num_ratings)
60+
result = client.get_compare_schools("1466", "1491")
61+
print(result.school_1.name, result.school_2.name)
3262
```
3363

34-
Fetch details and iterate ratings incrementally:
64+
**Iterate professor ratings** (first page from HTML, further pages via GraphQL):
3565

3666
```python
3767
from datetime import date
3868
from rmp_client import RMPClient
3969

4070
with RMPClient() as client:
41-
professor = client.get_professor("PROFESSOR_ID")
42-
43-
for rating in client.iter_professor_ratings(professor.id, since=date(2024, 1, 1)):
71+
for rating in client.iter_professor_ratings("2823076", since=date(2024, 1, 1)):
4472
print(rating.date, rating.quality, rating.comment)
4573
```
4674

75+
**Verify the client** (run the script to hit the live site and print sample data):
76+
77+
```bash
78+
pip install -e .
79+
python scripts/verify_client.py # up to 3 pages of ratings per section (default)
80+
python scripts/verify_client.py --max-pages 10 --page-size 20 # scrape more pages
81+
```
82+
83+
**Scrape all ratings** for a professor or school: the client fetches the first page from HTML and subsequent pages via the site’s GraphQL API. Use the iterators to get every rating:
84+
85+
```python
86+
with RMPClient() as client:
87+
for rating in client.iter_professor_ratings("2823076"):
88+
print(rating.date, rating.comment)
89+
for rating in client.iter_school_ratings("1466"):
90+
print(rating.date, rating.comment)
91+
```
92+
4793
## How it works
4894

4995
### Package architecture
@@ -68,43 +114,57 @@ flowchart TB
68114
end
69115
70116
subgraph External
71-
API["RMP GraphQL API\n(ratemyprofessors.com)"]
117+
RMP["RMP pages\n(ratemyprofessors.com)"]
72118
end
73119
74120
User --> Client
75121
Client --> Config
76122
Client --> HttpCtx
77123
HttpCtx --> Http
78124
Http --> Bucket
79-
Http --> API
125+
Http --> RMP
80126
Client --> Models
81127
Client --> Errors
82128
```
83129

84130
### Request flow
85131

132+
Professor, school, compare-schools, and search endpoints **fetch the relevant RMP page HTML** (GET), extract `window.__RELAY_STORE__` from the response, and parse it into `Professor`, `School`, `Rating`, or search result lists.
133+
134+
**Ratings pagination (Relay):** The first page of professor or school ratings comes from the same HTML (Relay store). The store’s connection includes:
135+
136+
- **`pageInfo.endCursor`** — opaque cursor for “start after this item”
137+
- **`pageInfo.hasNextPage`** — whether more ratings exist
138+
139+
The client then requests the next page by POSTing to `/graphql` with the same query and variables:
140+
141+
- `id` — Relay node id (base64 of `Teacher-{legacyId}` or `School-{legacyId}`)
142+
- `first` — page size (e.g. 20)
143+
- `after``pageInfo.endCursor` from the previous response
144+
145+
Loop until `hasNextPage` is false. The cursor is typically base64 for an internal offset (e.g. `YXJyYXljb25uZWN0aW9uOjQ=` decodes to `arrayconnection:4`, meaning “after item 4”). RMP does not rotate or expire these cursors, so you can paginate with plain HTTP requests without a browser. This client sends the **full GraphQL query** in each request; if the site ever required persisted queries (e.g. `doc_id` only), you’d capture the real request from the browser and reuse that format.
146+
86147
```mermaid
87148
sequenceDiagram
88149
participant User
89150
participant RMPClient
90151
participant HttpClient
91152
participant TokenBucket
92153
participant httpx
93-
participant RMP API
154+
participant RMP
94155
95-
User->>RMPClient: e.g. get_professor(id) or iter_professors_for_school(school_id)
96-
RMPClient->>RMPClient: Build GraphQL-style payload
97-
RMPClient->>HttpClient: post_json(path, payload)
156+
User->>RMPClient: e.g. get_professor(id), get_school(id), search_professors(q), get_compare_schools(id1, id2)
157+
RMPClient->>HttpClient: get_html(url)
98158
HttpClient->>TokenBucket: consume()
99159
TokenBucket-->>HttpClient: (blocks until token available)
100-
HttpClient->>httpx: POST base_url, json=payload
101-
httpx->>RMP API: HTTPS request
102-
RMP API-->>httpx: JSON response
160+
HttpClient->>httpx: GET page URL
161+
httpx->>RMP: HTTPS request
162+
RMP-->>httpx: HTML (with __RELAY_STORE__)
103163
httpx-->>HttpClient: response
104-
HttpClient->>HttpClient: Retry on 5xx / HTTP error
105-
HttpClient-->>RMPClient: dict (parsed JSON)
106-
RMPClient->>RMPClient: Parse into Professor / Rating / etc.
107-
RMPClient-->>User: Professor, Rating, or list
164+
HttpClient-->>RMPClient: HTML text
165+
RMPClient->>RMPClient: Extract and parse __RELAY_STORE__, resolve refs
166+
RMPClient->>RMPClient: Map to Professor / School / Rating / SearchResult
167+
RMPClient-->>User: Professor, School, list, or CompareSchoolsResult
108168
```
109169

110170
### Data models
@@ -117,9 +177,9 @@ erDiagram
117177
School {
118178
string id
119179
string name
120-
string city
121-
string state
122-
string country
180+
string location
181+
float overall_quality
182+
int num_ratings
123183
}
124184
125185
Professor {
@@ -141,9 +201,21 @@ erDiagram
141201
142202
ProfessorSearchResult {
143203
Professor[] professors
144-
int page
145-
int page_size
204+
int total
146205
bool has_next_page
206+
string next_cursor
207+
}
208+
209+
SchoolSearchResult {
210+
School[] schools
211+
int total
212+
bool has_next_page
213+
string next_cursor
214+
}
215+
216+
CompareSchoolsResult {
217+
School school_1
218+
School school_2
147219
}
148220
149221
ProfessorRatingsPage {

0 commit comments

Comments
 (0)